{"name":"@playlive/realtime-pipeline","dist-tags":{"latest":"0.3.2"},"versions":{"0.3.0":{"name":"@playlive/realtime-pipeline","version":"0.3.0","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.17"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-4Sb8w2kfmm+AVCHVc64IfX31I21pInkopWobUoY9+g9OISWIh/jJ4r50udivDy4Xo+g9aNNr7Ue6MkGM3Bhkgg==","shasum":"5c243d35e4197a464c676f54a48e7ce282a4dd65","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.3.0.tgz","shasum":"5c243d35e4197a464c676f54a48e7ce282a4dd65","integrity":"sha512-4Sb8w2kfmm+AVCHVc64IfX31I21pInkopWobUoY9+g9OISWIh/jJ4r50udivDy4Xo+g9aNNr7Ue6MkGM3Bhkgg=="}},"0.1.0":{"name":"@playlive/realtime-pipeline","version":"0.1.0","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"bun":"./src/index.ts","import":"./dist/index.js","types":"./dist/index.d.ts"},"./protocol":{"bun":"./src/protocol/index.ts","import":"./dist/protocol/index.js","types":"./dist/protocol/index.d.ts"},"./reducer":{"bun":"./src/reducer/index.ts","import":"./dist/reducer/index.js","types":"./dist/reducer/index.d.ts"},"./store":{"bun":"./src/store/index.ts","import":"./dist/store/index.js","types":"./dist/store/index.d.ts"},"./connection":{"bun":"./src/connection/index.ts","import":"./dist/connection/index.js","types":"./dist/connection/index.d.ts"},"./config":{"bun":"./src/config/index.ts","import":"./dist/config/index.js","types":"./dist/config/index.d.ts"},"./demo":{"bun":"./src/demo/index.ts","import":"./dist/demo/index.js","types":"./dist/demo/index.d.ts"}},"files":["dist","coverage-badge.svg","README.md","CHANGELOG.md"],"scripts":{"build":"bun run build.ts","test":"bun test","coverage":"bun run ../../scripts/generate-coverage-badge.ts --cwd . --label Coverage","docs:build":"typedoc --out dist/docs src/index.ts","clean":"rm -rf dist","release":"release-it --config ../../.release-it.json","release:dry":"release-it --config ../../.release-it.json --dry-run"},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"0.1.0"},"peerDependencies":{"zustand":"^5.0.0"},"devDependencies":{"@playlive/dev-greenroom":"0.0.0","@types/bun":"latest","typescript":"^5.6.0","zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-EeJ1xa3wO6aXxdb/jgElkV3XZHLZGIvTHUuKgSqxY8VB4EH3fWTHxrQO+Knpe9Mn9BUsFPM8E3iSeNpAvLPfuw==","shasum":"ed69078d23f866f09b9cf541a69efa369468552c","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.1.0.tgz","shasum":"ed69078d23f866f09b9cf541a69efa369468552c","integrity":"sha512-EeJ1xa3wO6aXxdb/jgElkV3XZHLZGIvTHUuKgSqxY8VB4EH3fWTHxrQO+Knpe9Mn9BUsFPM8E3iSeNpAvLPfuw=="}},"0.1.1":{"name":"@playlive/realtime-pipeline","version":"0.1.1","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.1.1"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-gyRr0AakV/418FG8pNErpo3yED8EBPislyHjrVUCjY3TL8QsLPOyKy5dZ2nYpUxtrYdK7x4zNHJYlPrQYLgr/w==","shasum":"af1af583a87f21f9e0a645a34dc579a2b8b778fb","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.1.1.tgz","shasum":"af1af583a87f21f9e0a645a34dc579a2b8b778fb","integrity":"sha512-gyRr0AakV/418FG8pNErpo3yED8EBPislyHjrVUCjY3TL8QsLPOyKy5dZ2nYpUxtrYdK7x4zNHJYlPrQYLgr/w=="}},"0.2.0":{"name":"@playlive/realtime-pipeline","version":"0.2.0","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.1.1"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-hk1KteSj3TMO+sZ3p3CnoYJzzLPE/O10Qidbxnwk5jDKhrrPQ0xc/+h0oQMWf+W4Ct2pM4r21qzvo8GVFNSExg==","shasum":"be308781b47002a81fc69101b765e96ff83f86da","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.0.tgz","shasum":"be308781b47002a81fc69101b765e96ff83f86da","integrity":"sha512-hk1KteSj3TMO+sZ3p3CnoYJzzLPE/O10Qidbxnwk5jDKhrrPQ0xc/+h0oQMWf+W4Ct2pM4r21qzvo8GVFNSExg=="}},"0.2.1":{"name":"@playlive/realtime-pipeline","version":"0.2.1","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.4"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-FcqNNWPJTNrWFwLKFL0w/DW9plgs/Q4JZjXQ2/18eHyQVq8ZB/OYMTA4Nzq4hUbMjZA7tDMkkeZ5bye7D/JvkA==","shasum":"de57cfbcc9d053bd29b55ebcecf74069ea9a5c86","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.1.tgz","shasum":"de57cfbcc9d053bd29b55ebcecf74069ea9a5c86","integrity":"sha512-FcqNNWPJTNrWFwLKFL0w/DW9plgs/Q4JZjXQ2/18eHyQVq8ZB/OYMTA4Nzq4hUbMjZA7tDMkkeZ5bye7D/JvkA=="}},"0.2.2":{"name":"@playlive/realtime-pipeline","version":"0.2.2","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-EBK4XkVUtzz0bTCuC29xO9ecnrDP7DnCST0deNvr7ElhYuCH5gt/VcCQf9i6e/qWoHFyC5JV2Pug4yxMBPmKfA==","shasum":"ba8f678c89b190c32bde7aa90f2ce0258e4ee27e","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.2.tgz","shasum":"ba8f678c89b190c32bde7aa90f2ce0258e4ee27e","integrity":"sha512-EBK4XkVUtzz0bTCuC29xO9ecnrDP7DnCST0deNvr7ElhYuCH5gt/VcCQf9i6e/qWoHFyC5JV2Pug4yxMBPmKfA=="}},"0.2.3":{"name":"@playlive/realtime-pipeline","version":"0.2.3","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-YBCFBgESOVs+wXF7eNHNelDG24vuQXlV+zqi+TTike97PL9ISqL6KhqIfPa4Ssxvkr1P1Jnw3oTp+WKctfE1Bg==","shasum":"8d257582d08366ca1515c44bb98a44671d264cb1","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.3.tgz","shasum":"8d257582d08366ca1515c44bb98a44671d264cb1","integrity":"sha512-YBCFBgESOVs+wXF7eNHNelDG24vuQXlV+zqi+TTike97PL9ISqL6KhqIfPa4Ssxvkr1P1Jnw3oTp+WKctfE1Bg=="}},"0.2.4":{"name":"@playlive/realtime-pipeline","version":"0.2.4","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-X4gxPmFU+RZKPjI1hig30i+rq1IKzkBzYCIv5oyXGU2TjPs2DyuPpeQCQZPIgr2va8FW8LRPVSHq/UIu3q7rhQ==","shasum":"f3b1fbca62f039bfbd71684e1f3cccdaa6985a28","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.4.tgz","shasum":"f3b1fbca62f039bfbd71684e1f3cccdaa6985a28","integrity":"sha512-X4gxPmFU+RZKPjI1hig30i+rq1IKzkBzYCIv5oyXGU2TjPs2DyuPpeQCQZPIgr2va8FW8LRPVSHq/UIu3q7rhQ=="}},"0.2.5":{"name":"@playlive/realtime-pipeline","version":"0.2.5","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-/SbSbNwI8rUJAgBJJsT7oWpVovcqaWQlapQqjDDhoYQwZgubrrTSl+9qEcIvGiVgy+VDAU3wg0CZ/0hvpMUqeQ==","shasum":"cda773189b5327b4b335c3afbcc9f359401e93d9","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.5.tgz","shasum":"cda773189b5327b4b335c3afbcc9f359401e93d9","integrity":"sha512-/SbSbNwI8rUJAgBJJsT7oWpVovcqaWQlapQqjDDhoYQwZgubrrTSl+9qEcIvGiVgy+VDAU3wg0CZ/0hvpMUqeQ=="}},"0.2.6":{"name":"@playlive/realtime-pipeline","version":"0.2.6","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-85ZwJNUqFHRwV3II4Shkl+JBEn6k89tkU4o1cEc5qSgqMc4tf3bgh3lL9u4B6BC4WxAeHtSXzrfxykXQc60Ydg==","shasum":"656237a14ee04ae092255dbfb11fd9f6d02c6e82","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.6.tgz","shasum":"656237a14ee04ae092255dbfb11fd9f6d02c6e82","integrity":"sha512-85ZwJNUqFHRwV3II4Shkl+JBEn6k89tkU4o1cEc5qSgqMc4tf3bgh3lL9u4B6BC4WxAeHtSXzrfxykXQc60Ydg=="}},"0.2.7":{"name":"@playlive/realtime-pipeline","version":"0.2.7","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-UMslOOHX28wWuXCAyOAJH8RIVKz4Jcy0OsdJxW7fV0SyWeEVLBNRaBf47y5WHWjZcuAgT4JfTc5qirZ9UiRNnA==","shasum":"b607c13cf1e8471c11962768cdbe1060ba8c98ed","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.7.tgz","shasum":"b607c13cf1e8471c11962768cdbe1060ba8c98ed","integrity":"sha512-UMslOOHX28wWuXCAyOAJH8RIVKz4Jcy0OsdJxW7fV0SyWeEVLBNRaBf47y5WHWjZcuAgT4JfTc5qirZ9UiRNnA=="}},"0.2.8":{"name":"@playlive/realtime-pipeline","version":"0.2.8","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.11"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-MGS1ODuMDhaA99Rqmx2H/CfpJ9lczOcsRGn2KDeIekhEiT6iu6Zn3OWEvFx+gLUHHCCyR/CRJ4aZM6liDL0A2w==","shasum":"f82389e5ab135c4481229bef2f2e2c55dda780ef","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.8.tgz","shasum":"f82389e5ab135c4481229bef2f2e2c55dda780ef","integrity":"sha512-MGS1ODuMDhaA99Rqmx2H/CfpJ9lczOcsRGn2KDeIekhEiT6iu6Zn3OWEvFx+gLUHHCCyR/CRJ4aZM6liDL0A2w=="}},"0.2.9":{"name":"@playlive/realtime-pipeline","version":"0.2.9","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.13"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-jA+irR14FPfNk2vpc0JisUenJ8DGoBleJPgjpxs6PQn9sLNcTqenJC7W6KvV48UhNozt4MdJ+QCKtNQQaLj+oA==","shasum":"0557b46f40afa79531fd39ff464c4a9f837b8356","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.9.tgz","shasum":"0557b46f40afa79531fd39ff464c4a9f837b8356","integrity":"sha512-jA+irR14FPfNk2vpc0JisUenJ8DGoBleJPgjpxs6PQn9sLNcTqenJC7W6KvV48UhNozt4MdJ+QCKtNQQaLj+oA=="}},"0.2.10":{"name":"@playlive/realtime-pipeline","version":"0.2.10","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.13"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-K4xeCrp1nOCTirOanPDMiT2lMm//ZSHK8p0Fs6v2Oy3L9GEEP8VS5RWECv5zLVoEaVj+Q81BBU8K6WMo2MQJvw==","shasum":"6b66e3c0b30495ef06e72d3f91107576d1e20b7e","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.10.tgz","shasum":"6b66e3c0b30495ef06e72d3f91107576d1e20b7e","integrity":"sha512-K4xeCrp1nOCTirOanPDMiT2lMm//ZSHK8p0Fs6v2Oy3L9GEEP8VS5RWECv5zLVoEaVj+Q81BBU8K6WMo2MQJvw=="}},"0.2.11":{"name":"@playlive/realtime-pipeline","version":"0.2.11","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.17"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-SE1X6nbq2SMxbH+7Q9QKlNq0Irk1pOX9qXy20t1yCDXRokSl0P6Y61ud5UDMZfqVgMvWjXOYt/tVkcIZaBk3XQ==","shasum":"39cb16600040a28991a74ad2f6fd4469eaf1f84c","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for\nthe Play Live unified data pipeline (UDP). Ported from\n`playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a tiny built-in reconnect state\nmachine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline\nbun add -d zustand\n```\n\n`zustand` is a **peer dependency** (jose-style — consumer brings their\nown copy). The package depends on `@playlive/tiltify-core` for the\nTiltify wire types only; that dep is bundled transitively when\ninstalled from CodeArtifact, no extra step required.\n\nNo other peer deps. Native `WebSocket` + native `fetch` only.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@user\",\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n```\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n## Subpath exports\n\n| Subpath                                | Description                                                    |\n| -------------------------------------- | -------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`          | Default barrel — re-exports everything below.                  |\n| `@playlive/realtime-pipeline/protocol` | Wire-format types only (zero runtime code).                    |\n| `@playlive/realtime-pipeline/reducer`  | Pure `protect*Amounts` + `merge*` helpers.                     |\n| `@playlive/realtime-pipeline/store`    | Bare `createPipelineStore` + `PipelineStore` interface.        |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, `Emitter`.     |\n| `@playlive/realtime-pipeline/config`   | `initializeDataLayer`, `getConfig`, `isInitialized`.           |\n| `@playlive/realtime-pipeline/demo`     | Demo fixtures + `isDemoMode` for offline overlay previews.     |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site\nat `dist/docs/`. The aggregate site (every package merged) is built\nvia `bun run docs:site` at the workspace root.\n\nTop-level exports:\n\n| Export                        | Source         | Notes                                                       |\n| ----------------------------- | -------------- | ----------------------------------------------------------- |\n| `createPipelineStore`         | `./store`      | Vanilla Zustand store factory.                              |\n| `createPipelineConnection`    | `./connection` | Native-`WebSocket` state machine + identify lifecycle.      |\n| `processMessage`              | `./connection` | Pure dispatcher — useful for test harnesses + GreenRoom.    |\n| `Emitter`                     | `./connection` | Tiny typed event emitter (no external dep).                 |\n| `buildIdentifyPayload`        | `./connection` | Pure identify payload builder.                              |\n| `buildRefreshPayload`         | `./connection` | Pure refresh-command builder.                               |\n| `isIdentifyReady`             | `./connection` | Predicate matching the legacy `useWebSocketManager` gating. |\n| `protect*Amounts`             | `./reducer`    | Monotonic-amount guards (campaign / team / event).          |\n| `mergePolls` + sibling helpers | `./reducer`   | `mergeById`-backed per-campaign accumulators.               |\n| `initializeDataLayer`         | `./config`     | Set the global proxy + WebSocket URLs.                      |\n| `isDemoMode` + demo fixtures  | `./demo`       | Offline demo data + slug-based detector.                    |\n| `PACKAGE_NAME`                | `./`           | Identifier for runtime version-pinning.                     |\n| `KNOWN_URLS`                  | `./`           | Twitch Extension URL disclosure list.                       |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data\npipeline — there's no external OpenAPI to drift against. When the\n`unified-data-pipeline` backend (separate repo) adds a new message\ntype, mirror it in `src/protocol/index.ts` and add a dispatch arm to\n`src/connection/processMessage.ts`. The protocol subpath was\nintentionally split out so the backend can one day depend on\n`@playlive/realtime-pipeline/protocol` as a wire-format source of\ntruth.\n\nTiltify v5 REST OpenAPI snapshots (consumed by `@playlive/tiltify-core`)\nlive at [`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can connect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })`, add\nthe override to your own Extension URL disclosure too — the auditor\nwalks `KNOWN_URLS` from every dep, but it can't see runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The\nReact-specific parts (`useUnifiedDataPipeline`,\n`UnifiedDataPipelineProvider`, `useWebSocketManager`, the\n`useAddPipeline*` family) land in `@playlive/react-pipeline` in\nphase 6. Every type, every reducer, and the entire store action\nsurface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection`\n  state machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\nThe `migrate-from-rusw` codemod skill (under\n`.agents/skills/migrate-from-rusw/`) rewrites existing\n`useWebSocket()` call sites that used `react-use-websocket-lite` to\nthe new React binding once phase 6 lands.\n\n## Examples\n\nRealistic end-to-end scenarios (donation train surge, subathon\nkick-off, donor spotlight rotation) land in `examples/` once\n`dev/greenroom` (phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new\nWebSocket message type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and\n   the `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the changelog + migration table.\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/realtime-pipeline/-/realtime-pipeline-0.2.11.tgz","shasum":"39cb16600040a28991a74ad2f6fd4469eaf1f84c","integrity":"sha512-SE1X6nbq2SMxbH+7Q9QKlNq0Irk1pOX9qXy20t1yCDXRokSl0P6Y61ud5UDMZfqVgMvWjXOYt/tVkcIZaBk3XQ=="}},"0.3.1":{"name":"@playlive/realtime-pipeline","version":"0.3.1","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.18"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-Sm9xcs6V2BYoN0a3pWzSlBM78sxxz+TVY5tdRkpDJAtXhEm6C1OVA/6PBB3/KhfXvzJ3lUTfRjr1cio5SQIJqA==","shasum":"bc6c25dec16901e37db7f5876e4dce31de319c11","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for the Play\nLive unified data pipeline (UDP). Owns the wire format every Play Live overlay\nspeaks: identify → `connection` echo → `tick` + incremental push messages.\nPorted from `playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a built-in reconnect state machine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline zustand\n```\n\n`zustand` (`^5.0.0`) is the **only peer dependency** — jose-style, the consumer\nbrings their own copy, and it must be a real runtime dependency, not a dev one.\n`@playlive/tiltify-core` is a regular dependency (Tiltify wire types only) and\ninstalls transitively; nothing extra to do.\n\nNative `WebSocket` + native `fetch` only. Runs in the browser, Bun, Node ≥ 22,\nand any framework. For React, use\n[`@playlive/react-pipeline`](../react-pipeline/) instead of wiring this by hand.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@playliver\",\n});\n\n// Client connections don't identify until overlay info is set\n// (`isIdentifyReady`); admin connections identify on an admin key alone.\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@playliver\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"identify\", (payload) => console.log(\"SENT identify:\", payload));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\nconn.on(\"reconnect-stop\", (attempts) => console.warn(`gave up after ${attempts}`));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n\n// Later, on teardown — removes listeners, timers, and the store subscription.\nconn.destroy();\n```\n\nIf `url` is omitted the connection uses `store.getState().wsURL`, which defaults\nto the `websocketUrl` from `initializeDataLayer()` when the config singleton has\nbeen initialised, and to `DEFAULT_WEBSOCKET_URL` otherwise.\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\nimport { createPipelineConnection } from \"@playlive/realtime-pipeline/connection\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n`createPipelineConnection` throws immediately when no constructor is available\nand none was supplied.\n\n## Subpath exports\n\n| Subpath                                  | Description                                                                 |\n| ---------------------------------------- | --------------------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`            | Default barrel — re-exports every subpath below plus `PACKAGE_NAME` / `KNOWN_URLS`. |\n| `@playlive/realtime-pipeline/protocol`   | Wire-format types only (zero runtime code except `CharityTypes`).           |\n| `@playlive/realtime-pipeline/reducer`    | Pure `protect*Amounts` + `merge*` helpers.                                  |\n| `@playlive/realtime-pipeline/store`      | `createPipelineStore` + `PipelineStore` / `PipelineStoreApi`.               |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, identify builders, session, `Emitter`. |\n| `@playlive/realtime-pipeline/config`     | `initializeDataLayer`, `getConfig`, `isInitialized`, `resetConfig`.         |\n| `@playlive/realtime-pipeline/demo`       | Demo fixtures + `isDemoMode` for offline overlay previews.                  |\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site at\n`dist/docs/`. The aggregate site (every package merged) is built via\n`bun run docs:site` at the workspace root.\n\n### Connection (`./connection`)\n\n| Export                     | Kind      | Notes                                                                                    |\n| -------------------------- | --------- | ---------------------------------------------------------------------------------------- |\n| `createPipelineConnection` | function  | `(options: PipelineConnectionOptions) => PipelineConnection`. Native-`WebSocket` state machine + identify lifecycle. |\n| `PipelineConnection`       | interface | Handle: `connect`, `disconnect`, `send`, `identify`, `refresh`, `on`, `off`, `readyState`, `destroy`. |\n| `PipelineConnectionOptions`| interface | `store` (required) + `url`, `webSocketCtor`, `maxReconnectAttempts` (100), `reconnectIntervalMs` (1500), `identifyDebounceMs` (50), `identifyDedupeMs` (500), `keepAliveIntervalMs` (60 000), `autoConnect` (false), `onCommandReload`, `log`. |\n| `PipelineConnectionEvents` | interface | Event map: `open`, `close`, `error`, `reconnect-stop`, `message`, `identify`.             |\n| `WebSocketCtor` / `WebSocketLike` | interface | Minimal DOM-free `WebSocket` shapes, for injecting `ws` or a mock.                |\n| `processMessage`           | function  | `(store, rawMessage: string, options?: ProcessMessageOptions) => void`. Pure dispatcher — never throws. |\n| `ProcessMessageOptions`    | interface | `onCommandReload`, `initialDataDelayMs` (250), `log`.                                    |\n| `Emitter<Events>`          | class     | Tiny typed event emitter (`on` returns an unsubscribe fn). No external dep.               |\n| `buildIdentifyPayload`     | function  | `(store: PipelineStoreApi) => IdentifyPayload`. Pure.                                     |\n| `IdentifyPayload`          | interface | The `action: \"identify\"` frame — see *Protocol* below.                                    |\n| `isIdentifyReady`          | function  | `(store) => boolean`. `(hasSetOverlayInfo || adminApiKey) && connected`.                  |\n| `buildRefreshPayload`      | function  | `(filters?: RefreshCommandFilters) => Record<string, unknown>`. Pure.                     |\n| `stableEqual`              | function  | `JSON.stringify`-based deep equality used to suppress spurious re-identifies.             |\n| `resolveOverlaySession`    | function  | `() => OverlaySession`. Per-tab identity stamped onto every identify.                     |\n| `resetOverlaySession`      | function  | Drops the memoised identity. For tests.                                                   |\n| `OverlaySession`           | interface | `{ id, label?, viewport? }` — `label` comes from `?session=`, capped at 24 chars.         |\n\n### Store (`./store`)\n\n| Export                | Kind      | Notes                                                                                  |\n| --------------------- | --------- | -------------------------------------------------------------------------------------- |\n| `createPipelineStore` | function  | `(initialProps?: InitialProps) => PipelineStoreApi`. Vanilla Zustand store factory.    |\n| `PipelineStore`       | interface | Full state + action surface exposed via `store.getState()`.                             |\n| `PipelineStoreApi`    | type      | `ReturnType<typeof createPipelineStore>` — what every helper here accepts.              |\n\nState slices worth knowing: `campaigns`, `teamCampaigns`, `fundraisingEvents`,\n`causes`, `donations` / `testDonations`, `polls`, `rewards`, `milestones`,\n`targets`, `leaderboardEntries`, `leaderboardExclusions`, `donationTrains`,\n`subathonTimers`, `donorSpotlights`, `donorSpotlightSettings`, `schedules`,\n`lastScheduleTransitions`, `lastPollDeltas` (keyed by poll ID, not campaign\nID), `auctionHouses`,\n`twitchChatMessages`, plus `connected` / `connecting` /\n`hasReceivedInitialData`.\n\nFresh-read getters (`getDonations`, `getPolls`, `getRewards`, `getMilestones`,\n`getTargets`, `getLeaderboardEntries`, `getLeaderboardExclusions`,\n`getDonationTrains`, `getSubathonTimers`, `getDonorSpotlight`, `getSchedule`,\n`getLastScheduleTransition`, `getFirstCampaign`, …) all take a campaign ID and\nnever return `undefined`. Actions prefixed `_` are wired by\n`createPipelineConnection` and are not part of the consumer contract.\n\n### Reducers (`./reducer`)\n\n| Export                                     | Signature                                                                       |\n| ------------------------------------------ | ------------------------------------------------------------------------------- |\n| `protectCampaignAmounts`                   | `(incoming: TiltifyCampaign[], existing: TiltifyCampaign[]) => TiltifyCampaign[]` |\n| `protectTeamCampaignAmounts`               | `(incoming: TiltifyTeamCampaign[], existing: TiltifyTeamCampaign[]) => TiltifyTeamCampaign[]` |\n| `protectFundraisingEventAmounts`           | `(incoming: TiltifyFundraisingEvent[], existing: TiltifyFundraisingEvent[]) => TiltifyFundraisingEvent[]` |\n| `mergePolls`                               | `(existing, incoming, authoritative?: boolean) => Record<string, TiltifyPoll[]>` |\n| `mergeRewards`                             | same shape, `TiltifyReward[]`                                                    |\n| `mergeMilestones`                          | same shape, `TiltifyMilestone[]`                                                 |\n| `mergeTargets`                             | same shape, `TiltifyTarget[]`                                                    |\n| `mergeLeaderboardEntries`                  | same shape, `TiltifyLeaderboardEntry[]`                                          |\n| `mergeFundraisingEventSupportingCampaigns` | `(existing, incoming) => Record<string, TiltifyCampaign[]>` — no `authoritative` flag; delegates to `protectCampaignAmounts`. |\n\nThe `protect*` helpers guarantee `amount_raised` / `total_amount_raised` never\ndecrease when a stale snapshot lands after a newer one. The `merge*` helpers\naccumulate by row id across ticks; pass `authoritative: true` (driven by\n`tick.data.authoritative`) to replace the named campaigns wholesale so rows\ndeleted upstream actually disappear.\n\n### Config (`./config`)\n\n| Export                  | Kind      | Notes                                                                        |\n| ----------------------- | --------- | ---------------------------------------------------------------------------- |\n| `initializeDataLayer`   | function  | `(config: DataLayerConfig) => void`. Idempotent; replaces any previous config. |\n| `getConfig`             | function  | `() => Required<DataLayerConfig>`. **Throws** if called before initialisation. |\n| `isInitialized`         | function  | `() => boolean`.                                                             |\n| `resetConfig`           | function  | Resets to uninitialised. For tests.                                          |\n| `DataLayerConfig`       | interface | `tiltifyProxyUrl` (required) + `twitchServiceUrl`, `websocketUrl`, `causeId`, `defaultPollingInterval`. |\n| `DEFAULT_CONFIG`        | const     | Defaults applied over every override.                                         |\n| `DEFAULT_WEBSOCKET_URL` | const     | `\"wss://main.playlive.ws.api.experience.stjude.org\"`.                        |\n\n### Demo (`./demo`)\n\n| Export                                                       | Kind     | Notes                                                                  |\n| ------------------------------------------------------------ | -------- | ---------------------------------------------------------------------- |\n| `isDemoMode`                                                  | function | `(userOrTeamSlug, campaignSlug) => boolean`. Two args, both nullable.  |\n| `isDemoCampaignId`                                            | function | `(id) => boolean` — matches the two all-zero demo UUIDs.               |\n| `DEMO_USER_SLUG` / `DEMO_TEAM_SLUG`                           | const    | `\"playliveDemoUser\"` / `\"playliveDemoTeam\"`.                           |\n| `DEMO_CAMPAIGN_SLUG` / `DEMO_TEAM_CAMPAIGN_SLUG`              | const    | Campaign slugs paired with the above.                                  |\n| `DEMO_CAMPAIGN_ID` / `DEMO_TEAM_CAMPAIGN_ID` / `DEMO_FUNDRAISING_EVENT_ID` / `DEMO_CAUSE_ID` | const | Stable fixture IDs.                        |\n| `DEMO_USER`, `DEMO_TEAM`, `DEMO_CAMPAIGN`, `DEMO_TEAM_CAMPAIGN`, `DEMO_FUNDRAISING_EVENT`, `DEMO_DONATIONS`, `DEMO_MILESTONES`, `DEMO_REWARDS`, `DEMO_POLL`, `DEMO_POLL_OPTIONS`, `DEMO_LEADERBOARD`, `DEMO_SCHEDULE` | const | Fixture data. |\n| `getDemoCampaign(isTeam)`, `getDemoUser()`, `getDemoTeam()`, `getDemoFundraisingEvent()`, `getDemoDonations()`, `getDemoMilestones()`, `getDemoRewards()`, `getDemoPoll()`, `getDemoLeaderboard()`, `getDemoSchedule(params?)`, `getDemoScheduleBlockRaised(params?)`, `getDemoDonorSpotlight(params)` | function | Accessors returning fresh copies. |\n| `buildDemoSchedule`, `getDemoBlockRaised`                     | function | Lower-level schedule builders (`BuildDemoScheduleParams`, `GetDemoBlockRaisedParams`). |\n| `DEFAULT_DEMO_SCHEDULE_INTERVAL_MS` / `MIN_DEMO_SCHEDULE_INTERVAL_MS` | const | `60_000` / `500`.                                            |\n\n### Package metadata (`./`)\n\n| Export         | Kind  | Notes                                        |\n| -------------- | ----- | -------------------------------------------- |\n| `PACKAGE_NAME` | const | Identifier for runtime version-pinning.      |\n| `KNOWN_URLS`   | const | Twitch Extension URL disclosure list.        |\n\n## Upstream spec\n\nThis package owns the wire format for the Play Live unified data pipeline —\nthere's no external OpenAPI to drift against. The `protocol` subpath was\nintentionally split out so the `playlive-unified-data-pipeline` backend can\ndepend on `@playlive/realtime-pipeline/protocol` as the wire-format source of\ntruth. See also [`docs/realtime-protocol.md`](../../docs/realtime-protocol.md).\n\nTiltify v5 REST OpenAPI snapshots (consumed by\n[`@playlive/tiltify-core`](../tiltify/core/), whose types the protocol module\nreferences) live at [`specs/tiltify/`](../../specs/tiltify/).\n\n### Connect / identify / dispatch flow\n\n```mermaid\nsequenceDiagram\n    participant App as Overlay\n    participant Conn as createPipelineConnection\n    participant Store as createPipelineStore\n    participant UDP as Pipeline server\n\n    App->>Store: setOverlayInfo / addCampaignID\n    App->>Conn: connect()\n    Conn->>UDP: WebSocket open\n    UDP-->>Conn: onopen\n    Conn->>Store: _setConnectionState(true, false)\n    Conn->>UDP: {\"action\":\"identify\", campaignIDs, overlayInfo, ...}\n    UDP-->>Conn: {\"type\":\"connection\", connection, subscriptions}\n    Conn->>Store: _updateSettingsFromServer / _setLastIdentityMessage\n    UDP-->>Conn: {\"type\":\"tick\", data:{...}}\n    Conn->>Store: _updateTickData -> hasReceivedInitialData after 250ms\n    UDP-->>Conn: {\"type\":\"updatePoll\"|\"updateLeaderboard\"|...}\n    Conn->>Store: per-message action\n    App->>Conn: refresh({campaignID})\n    Conn->>UDP: {\"action\":\"request\",\"command\":\"refresh\",...}\n    UDP-->>Conn: {\"type\":\"request:refresh\", data:{refreshed}}\n```\n\nAny state change that affects the identify payload (campaign IDs, overlay info,\ntiltify settings, admin key, feature toggles, twitch username) re-triggers a\ndebounced identify automatically via a store subscription. A 60 s keep-alive\nre-identify runs on top of that. Identical payloads sent within 500 ms are\nsuppressed.\n\n### Client → server frames\n\n| Frame                                            | Builder                | Notes                                                          |\n| ------------------------------------------------ | ---------------------- | -------------------------------------------------------------- |\n| `{ action: \"identify\", … }`                      | `buildIdentifyPayload` | `IdentifyPayload`: `campaignIDs`, `fundraisingEventIDs`, `causeIDs`, `teamCampaignIDs`, `type`, `tiltify`, `overlayInfo` (incl. `session`), `subathonTimer`, `donationTrains`, `donorSpotlight`, optional `adminApiKey` / `twitchUsername`. |\n| `{ action: \"request\", command: \"refresh\", … }`   | `buildRefreshPayload`  | Optional `campaignID` / `overlayName` / `overlayPath` filters (`RefreshCommandFilters`). Admin-only; no-op while disconnected. |\n\n### Server → client messages\n\nEvery typed message extends `WebSocketMessage` (`{ type, id, sourceType }`).\nThis is the complete set `processMessage` dispatches — anything else is\nsilently dropped, which is what makes new message types safe to roll out ahead\nof client upgrades.\n\n| `type`                          | Exported interface                    | Store effect                                                   |\n| ------------------------------- | ------------------------------------- | -------------------------------------------------------------- |\n| `connection`                    | `ConnectionMessage`                   | Normalised settings echo + `connectionType` + webhook subscriptions. |\n| `tick`                          | `TiltifyTickMessage`                  | Bulk aggregate update (`TiltifyTickMessageData`); flips `hasReceivedInitialData` after 250 ms. |\n| `public:direct:donation_updated`| `DonationMessage`                     | Appends to `donations[campaignID]` — or `testDonations` when `sourceType === \"test\"`. |\n| `public:direct:fact_updated`    | `CampaignMessage`                     | Merges a campaign / team campaign. `sourceType === \"reset\"` opts into `allowDecrease`. |\n| `updateDonationTrain`           | `DonationTrainUpdatedMessage`         | Upserts `donationTrains[campaignID]`.                          |\n| `deleteDonationTrain`           | `DonationTrainDeletedMessage`         | Removes the train.                                             |\n| `updateSubathonTimer`           | `SubathonTimerUpdatedMessage`         | Upserts `subathonTimers[campaignID]`.                          |\n| `updateDonorSpotlight`          | `DonorSpotlightUpdatedMessage`        | Replaces `donorSpotlights[campaignID]` (`ComputedSpotlight`).  |\n| `updateDonorSpotlightSettings`  | `DonorSpotlightSettingsUpdatedMessage`| Replaces `donorSpotlightSettings[campaignID]`.                 |\n| `updatePoll`                    | `UpdatePollMessage`                   | Upserts one poll in `polls[campaignID]` + records `lastPollDeltas[poll.id]` (`PollOptionDelta[]`; the field is omitted on a poll's first push, and the store then leaves the prior deltas untouched). |\n| `updateLeaderboard`             | `UpdateLeaderboardMessage`            | Replaces `leaderboardEntries[campaignID]` wholesale.           |\n| `updateLeaderboardExclusions`   | `UpdateLeaderboardExclusionsMessage`  | Replaces `leaderboardExclusions[campaignID]` wholesale.        |\n| `updateSchedule`                | `UpdateScheduleMessage`               | Replaces `schedules[campaignID]` with `ScheduleTransitionBlock[]`. |\n| `request:refresh`               | `RefreshResponseMessage`              | Stores `lastRefreshResponse` (`{ refreshed: number }`).        |\n| `admin:webhook:firehose`        | `AdminWebhookFirehoseMessage`         | Union of `TiltifyFirehoseMessage` \\| `TwitchFirehoseMessage`. Admin connections only. |\n| `twitch:chat:message`           | `TwitchChatMessage`                   | Appends to `twitchChatMessages`.                               |\n| `command:reload`                | *(no interface — bare `{ type }`)*    | Invokes `options.onCommandReload`; defaults to `window.location.reload()` in a browser. |\n\n`WebSocketErrorMessage` (`{ message: \"Internal server error\", connectionId,\nrequestId }`) has no `type` field — `processMessage` logs it via `options.log`\nand drops it.\n\nSupporting payload types exported from the same module: `CharityTypes` /\n`CharityType`, `TiltifyWebSocketSettings`, `TiltifyWebhookSubscription`,\n`WebSocketConnection` / `TiltifyWebSocketConnection` /\n`TwitchWebSocketConnection`, `ConnectionMessageSubscriptions`, `DonationTrain`\n+ `DonationTrainRuleset` / `PartialDonationTrainRuleset` / `TrainDonation` /\n`DonationTrainStatus`, `SubathonTimer` + `SubathonTimerDonation` /\n`SubathonTimerEffect` / `SubathonTimerStatus`, the donor-spotlight family\n(`ComputedSpotlight`, `DonorOfTheHour`, `BiggestDonation`, `CommunityHero`,\n`DonorSpotlightSettings`, `PartialDonorSpotlightSettings`,\n`DonorSpotlightDonation` and their `Raw*` counterparts), `PollOptionDelta`,\n`LeaderboardExclusion`, `AuctionHouse`, `TiltifyDonationWithTestFlag`,\n`TiltifyAuthoritativeSlice`, `TiltifyTickMessageData`,\n`ScheduleTransitionBlock`, `ScheduleTransitionPayload`,\n`RefreshCommandFilters`, `InitialProps`, and `OverlayInfo`.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this package can\nconnect to. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })` — or via\n`createPipelineConnection({ url })` — add the override to your own Extension\nURL disclosure too: the auditor walks `KNOWN_URLS` from every dep, but it can't\nsee runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The React-specific\nparts (`useUnifiedDataPipeline`, `UnifiedDataPipelineProvider`,\n`useWebSocketManager`, the `useAddPipeline*` family) live in\n[`@playlive/react-pipeline`](../react-pipeline/). Every type, every reducer,\nand the entire store action surface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection` state\n  machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full per-symbol\ntable, and\n[`docs/overlay-data-layer-migration.md`](../../docs/overlay-data-layer-migration.md)\nfor the consumer-facing guide.\n\nThe [`migrate-from-rusw`](../../.agents/skills/migrate-from-rusw/) codemod skill\nrewrites existing `useWebSocket()` call sites that used\n`react-use-websocket-lite` to the new React binding.\n\n## Examples\n\n### Donation-train overlay, end to end\n\nConnect, wait for the first tick, then render the active train and react to\nevery subsequent push. This is the full shape of a real overlay minus the DOM.\n\n```ts\nimport {\n  createPipelineConnection,\n  createPipelineStore,\n  initializeDataLayer,\n  type DonationTrain,\n  type PipelineStoreApi,\n} from \"@playlive/realtime-pipeline\";\n\nconst CAMPAIGN_ID = \"abc-123\";\n\ninitializeDataLayer({\n  tiltifyProxyUrl: \"https://tiltify-proxy.prod.experience.stjude.org\",\n  websocketUrl: \"wss://main.playlive.ws.api.experience.stjude.org\",\n});\n\nconst store: PipelineStoreApi = createPipelineStore({\n  initialCampaignIDs: [CAMPAIGN_ID],\n  initialOverlayName: \"donation-train\",\n  initialOverlayPath: \"/overlays/donation-train\",\n  initialTeamUserSlug: \"@playliver\",\n  initialDonationTrainsEnabled: true,\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-train\",\n  teamUserSlug: \"@playliver\",\n  config: { theme: \"dark\" },\n  path: \"/overlays/donation-train\",\n});\n\n// Opt into the server-side slices this overlay actually needs. The second\n// argument re-sends the settings to the server on the next identify.\nstore.getState().updateTiltifySettings({ milestones: true, polls: false }, true);\n\nconst conn = createPipelineConnection({\n  store,\n  autoConnect: true, // url falls back to store.wsURL (from initializeDataLayer)\n  log: (msg, payload) => console.debug(\"[pipeline]\", msg, payload),\n});\n\nconn.on(\"error\", (event) => console.error(\"socket error\", event));\nconn.on(\"reconnect-stop\", (attempts) =>\n  console.error(`pipeline unreachable after ${attempts} attempts`),\n);\n\nfunction render(train: DonationTrain | undefined) {\n  if (!train || !train.trainVisible) return;\n  console.log(\n    `${train.trainStatus}: ${train.donationCount} donations, $${train.donationValue}`,\n  );\n}\n\nlet lastTrainId: string | null = null;\nstore.subscribe((s) => {\n  if (!s.hasReceivedInitialData) return;\n\n  const [train] = s.getDonationTrains(CAMPAIGN_ID, \"ACTIVE\");\n  if (train && train.id !== lastTrainId) {\n    lastTrainId = train.id;\n    console.log(\"new train started\", train.trainStart);\n  }\n  render(train);\n});\n\n// Operator-triggered resync (admin connections only — no-op otherwise).\nexport function forceResync() {\n  conn.refresh({ campaignID: CAMPAIGN_ID });\n}\n\nexport function teardown() {\n  conn.destroy();\n}\n```\n\n### Driving the store without a socket\n\n`processMessage` is the same pure dispatcher the connection uses, so tests,\nreplays, and the `dev/greenroom` simulator can feed the store directly:\n\n```ts\nimport { createPipelineStore } from \"@playlive/realtime-pipeline/store\";\nimport { processMessage } from \"@playlive/realtime-pipeline/connection\";\n\nconst store = createPipelineStore({ initialCampaignIDs: [\"c1\"] });\n\nprocessMessage(\n  store,\n  JSON.stringify({\n    type: \"public:direct:donation_updated\",\n    id: \"msg-1\",\n    sourceType: \"live\",\n    data: {\n      id: \"donation-1\",\n      campaign_id: \"c1\",\n      fundraising_event_id: null,\n      donor_name: \"Viewer1\",\n      amount: { value: \"25.00\", currency: \"USD\" },\n      completed_at: \"2025-01-01T00:00:00Z\",\n    },\n  }),\n);\n\nconsole.log(store.getState().getDonations(\"c1\").length); // 1\n\n// Unknown types are dropped, invalid JSON is dropped — never throws.\nprocessMessage(store, '{\"type\":\"someFutureMessage\"}');\nprocessMessage(store, \"not json\");\n```\n\n### Offline demo mode\n\nOverlays render demo content when the URL carries the demo slugs, with no\nnetwork access at all:\n\n```ts\nimport {\n  getDemoCampaign,\n  getDemoDonations,\n  isDemoMode,\n} from \"@playlive/realtime-pipeline/demo\";\n\n// /overlays/donation-bar/tiltify/@playliveDemoUser/playliveDemoCampaign\nconst [userOrTeamSlug, campaignSlug] = [\"playliveDemoUser\", \"playliveDemoCampaign\"];\n\nif (isDemoMode(userOrTeamSlug, campaignSlug)) {\n  const campaign = getDemoCampaign(false); // true → team campaign\n  const donations = getDemoDonations();\n  console.log(campaign.name, donations.length);\n} else {\n  // …boot the real pipeline connection\n}\n```\n\nSee [`docs/demo-mode.md`](../../docs/demo-mode.md) for the full slug matrix.\n\n### Injecting a mock socket in tests\n\n`WebSocketLike` / `WebSocketCtor` are exported precisely so tests never touch\nthe network — the package's own suite drives the state machine this way:\n\n```ts\nimport {\n  createPipelineConnection,\n  createPipelineStore,\n  type WebSocketCtor,\n  type WebSocketLike,\n} from \"@playlive/realtime-pipeline\";\n\nclass MockSocket implements WebSocketLike {\n  static instances: MockSocket[] = [];\n  static readonly CONNECTING = 0;\n  static readonly OPEN = 1;\n  static readonly CLOSING = 2;\n  static readonly CLOSED = 3;\n\n  readyState = 0;\n  onopen: ((ev: unknown) => void) | null = null;\n  onclose: ((ev: unknown) => void) | null = null;\n  onerror: ((ev: unknown) => void) | null = null;\n  onmessage: ((ev: { data: unknown }) => void) | null = null;\n  sent: string[] = [];\n\n  constructor(public readonly url: string) {\n    MockSocket.instances.push(this);\n  }\n  send(data: string) {\n    this.sent.push(data);\n  }\n  close() {\n    this.readyState = 3;\n    this.onclose?.({ code: 1000, reason: \"\", wasClean: true });\n  }\n}\n\nconst store = createPipelineStore({\n  initialURL: \"ws://test.local\",\n  initialCampaignIDs: [\"c1\"],\n});\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: MockSocket as unknown as WebSocketCtor,\n  autoConnect: true,\n});\n\nconst socket = MockSocket.instances[0]!;\nsocket.readyState = 1;\nsocket.onopen?.({});\n\nconst identify = JSON.parse(socket.sent[0]!);\nconsole.log(identify.action, identify.campaignIDs); // \"identify\" [\"c1\"]\n\nconn.destroy();\n```\n\nFor a real end-to-end harness (live WebSocket server, scripted donation\nsurges, subathon kick-offs), boot `dev/greenroom` — see\n[`docs/greenroom-cookbook.md`](../../docs/greenroom-cookbook.md).\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new WebSocket\nmessage type:\n\n1. Add the typed envelope to `src/protocol/index.ts`.\n2. Add the store action (`_processFoo`) to `src/store/index.ts` and the\n   `PipelineStore` interface.\n3. Add the dispatch arm to `src/connection/processMessage.ts`.\n4. Add a unit test under `tests/unit/processMessage.test.ts`.\n5. Append a row to the *Server → client messages* table above, plus the\n   changelog + migration table.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live CodeArtifact\n(PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/realtime-pipeline/-/realtime-pipeline-0.3.1.tgz","shasum":"bc6c25dec16901e37db7f5876e4dce31de319c11","integrity":"sha512-Sm9xcs6V2BYoN0a3pWzSlBM78sxxz+TVY5tdRkpDJAtXhEm6C1OVA/6PBB3/KhfXvzJ3lUTfRjr1cio5SQIJqA=="}},"0.3.2":{"name":"@playlive/realtime-pipeline","version":"0.3.2","description":"Framework-agnostic native-WebSocket client + vanilla Zustand store for the Play Live unified data pipeline.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./protocol":{"import":"./protocol/index.js","types":"./protocol/index.d.ts"},"./reducer":{"import":"./reducer/index.js","types":"./reducer/index.d.ts"},"./store":{"import":"./store/index.js","types":"./store/index.d.ts"},"./connection":{"import":"./connection/index.js","types":"./connection/index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"}},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"dependencies":{"@playlive/tiltify-core":"^0.4.19"},"peerDependencies":{"zustand":"^5.0.0"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"integrity":"sha512-g7iv1Z0Bum/sG40PIM9xjC/iC9FG2uyadLRIoTIdDatZy2g/86VAMLygkAudDWxMjqvawduKG1hVu8hopAr6qQ==","shasum":"d2219ba3a29d2b0596adf15eb1a63c4328f1fc53","readme":"# @playlive/realtime-pipeline\n\nFramework-agnostic native-WebSocket client + vanilla Zustand store for the Play\nLive unified data pipeline (UDP). Owns the wire format every Play Live overlay\nspeaks: identify → `connection` echo → `tick` + incremental push messages.\nPorted from `playlive-overlay-data-layer` with the React glue stripped out and\n`react-use-websocket-lite` replaced by a built-in reconnect state machine.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/realtime-pipeline zustand\n```\n\n`zustand` (`^5.0.0`) is the **only peer dependency** — jose-style, the consumer\nbrings their own copy, and it must be a real runtime dependency, not a dev one.\n`@playlive/tiltify-core` is a regular dependency (Tiltify wire types only) and\ninstalls transitively; nothing extra to do.\n\nNative `WebSocket` + native `fetch` only. Runs in the browser, Bun, Node ≥ 22,\nand any framework. For React, use\n[`@playlive/react-pipeline`](../react-pipeline/) instead of wiring this by hand.\n\n## Quick start\n\n### Vanilla — Node / Bun / browser script\n\n```ts\nimport {\n  createPipelineStore,\n  createPipelineConnection,\n} from \"@playlive/realtime-pipeline\";\n\nconst store = createPipelineStore({\n  initialCampaignIDs: [\"abc-123\"],\n  initialOverlayName: \"donation-bar\",\n  initialOverlayPath: \"/overlays/donation-bar\",\n  initialTeamUserSlug: \"@playliver\",\n});\n\n// Client connections don't identify until overlay info is set\n// (`isIdentifyReady`); admin connections identify on an admin key alone.\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@playliver\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  url: \"wss://main.playlive.ws.api.experience.stjude.org\",\n  autoConnect: true,\n});\n\nconn.on(\"open\", () => console.log(\"pipeline connected\"));\nconn.on(\"identify\", (payload) => console.log(\"SENT identify:\", payload));\nconn.on(\"message\", (raw) => console.log(\"RECV:\", raw));\nconn.on(\"close\", (info) => console.log(\"disconnected\", info));\nconn.on(\"reconnect-stop\", (attempts) => console.warn(`gave up after ${attempts}`));\n\nstore.subscribe((s) => {\n  console.log(`raised: ${s.campaigns[0]?.amount_raised?.value}`);\n});\n\n// Later, on teardown — removes listeners, timers, and the store subscription.\nconn.destroy();\n```\n\nIf `url` is omitted the connection uses `store.getState().wsURL`, which defaults\nto the `websocketUrl` from `initializeDataLayer()` when the config singleton has\nbeen initialised, and to `DEFAULT_WEBSOCKET_URL` otherwise.\n\n### Older Node without a global `WebSocket`\n\n```ts\nimport WebSocketCtor from \"ws\";\nimport { createPipelineConnection } from \"@playlive/realtime-pipeline/connection\";\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: WebSocketCtor as unknown as typeof WebSocket,\n});\n```\n\n`createPipelineConnection` throws immediately when no constructor is available\nand none was supplied.\n\n## Subpath exports\n\n| Subpath                                  | Description                                                                 |\n| ---------------------------------------- | --------------------------------------------------------------------------- |\n| `@playlive/realtime-pipeline`            | Default barrel — re-exports every subpath below plus `PACKAGE_NAME` / `KNOWN_URLS`. |\n| `@playlive/realtime-pipeline/protocol`   | Wire-format types only (zero runtime code except `CharityTypes`).           |\n| `@playlive/realtime-pipeline/reducer`    | Pure `protect*Amounts` + `merge*` helpers.                                  |\n| `@playlive/realtime-pipeline/store`      | `createPipelineStore` + `PipelineStore` / `PipelineStoreApi`.               |\n| `@playlive/realtime-pipeline/connection` | `createPipelineConnection`, `processMessage`, identify builders, session, `Emitter`. |\n| `@playlive/realtime-pipeline/config`     | `initializeDataLayer`, `getConfig`, `isInitialized`, `resetConfig`.         |\n| `@playlive/realtime-pipeline/demo`       | Demo fixtures + `isDemoMode` for offline overlay previews.                  |\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/realtime-pipeline/docs/>\n\n### Connection (`./connection`)\n\n| Export                     | Kind      | Notes                                                                                    |\n| -------------------------- | --------- | ---------------------------------------------------------------------------------------- |\n| `createPipelineConnection` | function  | `(options: PipelineConnectionOptions) => PipelineConnection`. Native-`WebSocket` state machine + identify lifecycle. |\n| `PipelineConnection`       | interface | Handle: `connect`, `disconnect`, `send`, `identify`, `refresh`, `on`, `off`, `readyState`, `destroy`. |\n| `PipelineConnectionOptions`| interface | `store` (required) + `url`, `webSocketCtor`, `maxReconnectAttempts` (100), `reconnectIntervalMs` (1500), `identifyDebounceMs` (50), `identifyDedupeMs` (500), `keepAliveIntervalMs` (60 000), `autoConnect` (false), `onCommandReload`, `log`. |\n| `PipelineConnectionEvents` | interface | Event map: `open`, `close`, `error`, `reconnect-stop`, `message`, `identify`.             |\n| `WebSocketCtor` / `WebSocketLike` | interface | Minimal DOM-free `WebSocket` shapes, for injecting `ws` or a mock.                |\n| `processMessage`           | function  | `(store, rawMessage: string, options?: ProcessMessageOptions) => void`. Pure dispatcher — never throws. |\n| `ProcessMessageOptions`    | interface | `onCommandReload`, `initialDataDelayMs` (250), `log`.                                    |\n| `Emitter<Events>`          | class     | Tiny typed event emitter (`on` returns an unsubscribe fn). No external dep.               |\n| `buildIdentifyPayload`     | function  | `(store: PipelineStoreApi) => IdentifyPayload`. Pure.                                     |\n| `IdentifyPayload`          | interface | The `action: \"identify\"` frame — see *Protocol* below.                                    |\n| `isIdentifyReady`          | function  | `(store) => boolean`. `(hasSetOverlayInfo || adminApiKey) && connected`.                  |\n| `buildRefreshPayload`      | function  | `(filters?: RefreshCommandFilters) => Record<string, unknown>`. Pure.                     |\n| `stableEqual`              | function  | `JSON.stringify`-based deep equality used to suppress spurious re-identifies.             |\n| `resolveOverlaySession`    | function  | `() => OverlaySession`. Per-tab identity stamped onto every identify.                     |\n| `resetOverlaySession`      | function  | Drops the memoised identity. For tests.                                                   |\n| `OverlaySession`           | interface | `{ id, label?, viewport? }` — `label` comes from `?session=`, capped at 24 chars.         |\n\n### Store (`./store`)\n\n| Export                | Kind      | Notes                                                                                  |\n| --------------------- | --------- | -------------------------------------------------------------------------------------- |\n| `createPipelineStore` | function  | `(initialProps?: InitialProps) => PipelineStoreApi`. Vanilla Zustand store factory.    |\n| `PipelineStore`       | interface | Full state + action surface exposed via `store.getState()`.                             |\n| `PipelineStoreApi`    | type      | `ReturnType<typeof createPipelineStore>` — what every helper here accepts.              |\n\nState slices worth knowing: `campaigns`, `teamCampaigns`, `fundraisingEvents`,\n`causes`, `donations` / `testDonations`, `polls`, `rewards`, `milestones`,\n`targets`, `leaderboardEntries`, `leaderboardExclusions`, `donationTrains`,\n`subathonTimers`, `donorSpotlights`, `donorSpotlightSettings`, `schedules`,\n`lastScheduleTransitions`, `lastPollDeltas` (keyed by poll ID, not campaign\nID), `auctionHouses`,\n`twitchChatMessages`, plus `connected` / `connecting` /\n`hasReceivedInitialData`.\n\nFresh-read getters (`getDonations`, `getPolls`, `getRewards`, `getMilestones`,\n`getTargets`, `getLeaderboardEntries`, `getLeaderboardExclusions`,\n`getDonationTrains`, `getSubathonTimers`, `getDonorSpotlight`, `getSchedule`,\n`getLastScheduleTransition`, `getFirstCampaign`, …) all take a campaign ID and\nnever return `undefined`. Actions prefixed `_` are wired by\n`createPipelineConnection` and are not part of the consumer contract.\n\n### Reducers (`./reducer`)\n\n| Export                                     | Signature                                                                       |\n| ------------------------------------------ | ------------------------------------------------------------------------------- |\n| `protectCampaignAmounts`                   | `(incoming: TiltifyCampaign[], existing: TiltifyCampaign[]) => TiltifyCampaign[]` |\n| `protectTeamCampaignAmounts`               | `(incoming: TiltifyTeamCampaign[], existing: TiltifyTeamCampaign[]) => TiltifyTeamCampaign[]` |\n| `protectFundraisingEventAmounts`           | `(incoming: TiltifyFundraisingEvent[], existing: TiltifyFundraisingEvent[]) => TiltifyFundraisingEvent[]` |\n| `mergePolls`                               | `(existing, incoming, authoritative?: boolean) => Record<string, TiltifyPoll[]>` |\n| `mergeRewards`                             | same shape, `TiltifyReward[]`                                                    |\n| `mergeMilestones`                          | same shape, `TiltifyMilestone[]`                                                 |\n| `mergeTargets`                             | same shape, `TiltifyTarget[]`                                                    |\n| `mergeLeaderboardEntries`                  | same shape, `TiltifyLeaderboardEntry[]`                                          |\n| `mergeFundraisingEventSupportingCampaigns` | `(existing, incoming) => Record<string, TiltifyCampaign[]>` — no `authoritative` flag; delegates to `protectCampaignAmounts`. |\n\nThe `protect*` helpers guarantee `amount_raised` / `total_amount_raised` never\ndecrease when a stale snapshot lands after a newer one. The `merge*` helpers\naccumulate by row id across ticks; pass `authoritative: true` (driven by\n`tick.data.authoritative`) to replace the named campaigns wholesale so rows\ndeleted upstream actually disappear.\n\n### Config (`./config`)\n\n| Export                  | Kind      | Notes                                                                        |\n| ----------------------- | --------- | ---------------------------------------------------------------------------- |\n| `initializeDataLayer`   | function  | `(config: DataLayerConfig) => void`. Idempotent; replaces any previous config. |\n| `getConfig`             | function  | `() => Required<DataLayerConfig>`. **Throws** if called before initialisation. |\n| `isInitialized`         | function  | `() => boolean`.                                                             |\n| `resetConfig`           | function  | Resets to uninitialised. For tests.                                          |\n| `DataLayerConfig`       | interface | `tiltifyProxyUrl` (required) + `twitchServiceUrl`, `websocketUrl`, `causeId`, `defaultPollingInterval`. |\n| `DEFAULT_CONFIG`        | const     | Defaults applied over every override.                                         |\n| `DEFAULT_WEBSOCKET_URL` | const     | `\"wss://main.playlive.ws.api.experience.stjude.org\"`.                        |\n\n### Demo (`./demo`)\n\n| Export                                                       | Kind     | Notes                                                                  |\n| ------------------------------------------------------------ | -------- | ---------------------------------------------------------------------- |\n| `isDemoMode`                                                  | function | `(userOrTeamSlug, campaignSlug) => boolean`. Two args, both nullable.  |\n| `isDemoCampaignId`                                            | function | `(id) => boolean` — matches the two all-zero demo UUIDs.               |\n| `DEMO_USER_SLUG` / `DEMO_TEAM_SLUG`                           | const    | `\"playliveDemoUser\"` / `\"playliveDemoTeam\"`.                           |\n| `DEMO_CAMPAIGN_SLUG` / `DEMO_TEAM_CAMPAIGN_SLUG`              | const    | Campaign slugs paired with the above.                                  |\n| `DEMO_CAMPAIGN_ID` / `DEMO_TEAM_CAMPAIGN_ID` / `DEMO_FUNDRAISING_EVENT_ID` / `DEMO_CAUSE_ID` | const | Stable fixture IDs.                        |\n| `DEMO_USER`, `DEMO_TEAM`, `DEMO_CAMPAIGN`, `DEMO_TEAM_CAMPAIGN`, `DEMO_FUNDRAISING_EVENT`, `DEMO_DONATIONS`, `DEMO_MILESTONES`, `DEMO_REWARDS`, `DEMO_POLL`, `DEMO_POLL_OPTIONS`, `DEMO_LEADERBOARD`, `DEMO_SCHEDULE` | const | Fixture data. |\n| `getDemoCampaign(isTeam)`, `getDemoUser()`, `getDemoTeam()`, `getDemoFundraisingEvent()`, `getDemoDonations()`, `getDemoMilestones()`, `getDemoRewards()`, `getDemoPoll()`, `getDemoLeaderboard()`, `getDemoSchedule(params?)`, `getDemoScheduleBlockRaised(params?)`, `getDemoDonorSpotlight(params)` | function | Accessors returning fresh copies. |\n| `buildDemoSchedule`, `getDemoBlockRaised`                     | function | Lower-level schedule builders (`BuildDemoScheduleParams`, `GetDemoBlockRaisedParams`). |\n| `DEFAULT_DEMO_SCHEDULE_INTERVAL_MS` / `MIN_DEMO_SCHEDULE_INTERVAL_MS` | const | `60_000` / `500`.                                            |\n\n### Package metadata (`./`)\n\n| Export         | Kind  | Notes                                        |\n| -------------- | ----- | -------------------------------------------- |\n| `PACKAGE_NAME` | const | Identifier for runtime version-pinning.      |\n| `KNOWN_URLS`   | const | Twitch Extension URL disclosure list.        |\n\n## Upstream spec\n\nThe \"upstream spec\" for this package is the Play Live realtime WebSocket wire\nprotocol itself — there is no external OpenAPI document to drift against. The\ntables and diagram in this section are the authoritative public description of\nthat protocol: every frame the client sends, every message the server can push,\nand the store effect each one has.\n\n`@playlive/realtime-pipeline/protocol` is a types-only subpath, deliberately\nsplit out so both ends of the socket — overlays and the pipeline server — can\nimport the same wire-format declarations. Payload bodies reuse the Tiltify v5\nshapes from [`@playlive/tiltify-core`](../tiltify/core/).\n\nUnknown `type` values are dropped rather than treated as errors, so the server\ncan roll out new message types ahead of client upgrades.\n\n### Connect / identify / dispatch flow\n\n```mermaid\nsequenceDiagram\n    participant App as Overlay\n    participant Conn as createPipelineConnection\n    participant Store as createPipelineStore\n    participant UDP as Pipeline server\n\n    App->>Store: setOverlayInfo / addCampaignID\n    App->>Conn: connect()\n    Conn->>UDP: WebSocket open\n    UDP-->>Conn: onopen\n    Conn->>Store: _setConnectionState(true, false)\n    Conn->>UDP: {\"action\":\"identify\", campaignIDs, overlayInfo, ...}\n    UDP-->>Conn: {\"type\":\"connection\", connection, subscriptions}\n    Conn->>Store: _updateSettingsFromServer / _setLastIdentityMessage\n    UDP-->>Conn: {\"type\":\"tick\", data:{...}}\n    Conn->>Store: _updateTickData -> hasReceivedInitialData after 250ms\n    UDP-->>Conn: {\"type\":\"updatePoll\"|\"updateLeaderboard\"|...}\n    Conn->>Store: per-message action\n    App->>Conn: refresh({campaignID})\n    Conn->>UDP: {\"action\":\"request\",\"command\":\"refresh\",...}\n    UDP-->>Conn: {\"type\":\"request:refresh\", data:{refreshed}}\n```\n\nAny state change that affects the identify payload (campaign IDs, overlay info,\ntiltify settings, admin key, feature toggles, twitch username) re-triggers a\ndebounced identify automatically via a store subscription. A 60 s keep-alive\nre-identify runs on top of that. Identical payloads sent within 500 ms are\nsuppressed.\n\n### Client → server frames\n\n| Frame                                            | Builder                | Notes                                                          |\n| ------------------------------------------------ | ---------------------- | -------------------------------------------------------------- |\n| `{ action: \"identify\", … }`                      | `buildIdentifyPayload` | `IdentifyPayload`: `campaignIDs`, `fundraisingEventIDs`, `causeIDs`, `teamCampaignIDs`, `type`, `tiltify`, `overlayInfo` (incl. `session`), `subathonTimer`, `donationTrains`, `donorSpotlight`, optional `adminApiKey` / `twitchUsername`. |\n| `{ action: \"request\", command: \"refresh\", … }`   | `buildRefreshPayload`  | Optional `campaignID` / `overlayName` / `overlayPath` filters (`RefreshCommandFilters`). Admin-only; no-op while disconnected. |\n\n### Server → client messages\n\nEvery typed message extends `WebSocketMessage` (`{ type, id, sourceType }`).\nThis is the complete set `processMessage` dispatches — anything else is\nsilently dropped, which is what makes new message types safe to roll out ahead\nof client upgrades.\n\n| `type`                          | Exported interface                    | Store effect                                                   |\n| ------------------------------- | ------------------------------------- | -------------------------------------------------------------- |\n| `connection`                    | `ConnectionMessage`                   | Normalised settings echo + `connectionType` + webhook subscriptions. |\n| `tick`                          | `TiltifyTickMessage`                  | Bulk aggregate update (`TiltifyTickMessageData`); flips `hasReceivedInitialData` after 250 ms. |\n| `public:direct:donation_updated`| `DonationMessage`                     | Appends to `donations[campaignID]` — or `testDonations` when `sourceType === \"test\"`. |\n| `public:direct:fact_updated`    | `CampaignMessage`                     | Merges a campaign / team campaign. `sourceType === \"reset\"` opts into `allowDecrease`. |\n| `updateDonationTrain`           | `DonationTrainUpdatedMessage`         | Upserts `donationTrains[campaignID]`.                          |\n| `deleteDonationTrain`           | `DonationTrainDeletedMessage`         | Removes the train.                                             |\n| `updateSubathonTimer`           | `SubathonTimerUpdatedMessage`         | Upserts `subathonTimers[campaignID]`.                          |\n| `updateDonorSpotlight`          | `DonorSpotlightUpdatedMessage`        | Replaces `donorSpotlights[campaignID]` (`ComputedSpotlight`).  |\n| `updateDonorSpotlightSettings`  | `DonorSpotlightSettingsUpdatedMessage`| Replaces `donorSpotlightSettings[campaignID]`.                 |\n| `updatePoll`                    | `UpdatePollMessage`                   | Upserts one poll in `polls[campaignID]` + records `lastPollDeltas[poll.id]` (`PollOptionDelta[]`; the field is omitted on a poll's first push, and the store then leaves the prior deltas untouched). |\n| `updateLeaderboard`             | `UpdateLeaderboardMessage`            | Replaces `leaderboardEntries[campaignID]` wholesale.           |\n| `updateLeaderboardExclusions`   | `UpdateLeaderboardExclusionsMessage`  | Replaces `leaderboardExclusions[campaignID]` wholesale.        |\n| `updateSchedule`                | `UpdateScheduleMessage`               | Replaces `schedules[campaignID]` with `ScheduleTransitionBlock[]`. |\n| `request:refresh`               | `RefreshResponseMessage`              | Stores `lastRefreshResponse` (`{ refreshed: number }`).        |\n| `admin:webhook:firehose`        | `AdminWebhookFirehoseMessage`         | Union of `TiltifyFirehoseMessage` \\| `TwitchFirehoseMessage`. Admin connections only. |\n| `twitch:chat:message`           | `TwitchChatMessage`                   | Appends to `twitchChatMessages`.                               |\n| `command:reload`                | *(no interface — bare `{ type }`)*    | Invokes `options.onCommandReload`; defaults to `window.location.reload()` in a browser. |\n\n`WebSocketErrorMessage` (`{ message: \"Internal server error\", connectionId,\nrequestId }`) has no `type` field — `processMessage` logs it via `options.log`\nand drops it.\n\nSupporting payload types exported from the same module: `CharityTypes` /\n`CharityType`, `TiltifyWebSocketSettings`, `TiltifyWebhookSubscription`,\n`WebSocketConnection` / `TiltifyWebSocketConnection` /\n`TwitchWebSocketConnection`, `ConnectionMessageSubscriptions`, `DonationTrain`\n+ `DonationTrainRuleset` / `PartialDonationTrainRuleset` / `TrainDonation` /\n`DonationTrainStatus`, `SubathonTimer` + `SubathonTimerDonation` /\n`SubathonTimerEffect` / `SubathonTimerStatus`, the donor-spotlight family\n(`ComputedSpotlight`, `DonorOfTheHour`, `BiggestDonation`, `CommunityHero`,\n`DonorSpotlightSettings`, `PartialDonorSpotlightSettings`,\n`DonorSpotlightDonation` and their `Raw*` counterparts), `PollOptionDelta`,\n`LeaderboardExclusion`, `AuctionHouse`, `TiltifyDonationWithTestFlag`,\n`TiltifyAuthoritativeSlice`, `TiltifyTickMessageData`,\n`ScheduleTransitionBlock`, `ScheduleTransitionPayload`,\n`RefreshCommandFilters`, `InitialProps`, and `OverlayInfo`.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this package can\nconnect to, ready to paste into an Extension's manifest URL allowlist.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/realtime-pipeline\";\nconsole.log(KNOWN_URLS);\n// [\"wss://main.playlive.ws.api.experience.stjude.org\"]\n```\n\nIf your consumer overrides the WebSocket URL via\n`initializeDataLayer({ websocketUrl: \"wss://other.example/ws\" })` — or via\n`createPipelineConnection({ url })` — add the override to your own Extension\nURL disclosure too: the auditor walks `KNOWN_URLS` from every dep, but it can't\nsee runtime overrides.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/realtime-pipeline` is a one-for-one replacement for the\nframework-agnostic half of `playlive-overlay-data-layer`. The React-specific\nparts (`useUnifiedDataPipeline`, `UnifiedDataPipelineProvider`,\n`useWebSocketManager`, the `useAddPipeline*` family) live in\n[`@playlive/react-pipeline`](../react-pipeline/). Every type, every reducer,\nand the entire store action surface are preserved verbatim — only:\n\n- `createUDPStore` → `createPipelineStore`\n- `UDPStore` interface → `PipelineStore`\n- `UDPStoreApi` → `PipelineStoreApi`\n- `react-use-websocket-lite` → built-in `createPipelineConnection` state\n  machine (same defaults: 100 attempts, 1500 ms base, jittered).\n\nEvery other symbol keeps its name, so most migrations are a package rename plus\nthose four renames.\n\n## Examples\n\n### Donation-train overlay, end to end\n\nConnect, wait for the first tick, then render the active train and react to\nevery subsequent push. This is the full shape of a real overlay minus the DOM.\n\n```ts\nimport {\n  createPipelineConnection,\n  createPipelineStore,\n  initializeDataLayer,\n  type DonationTrain,\n  type PipelineStoreApi,\n} from \"@playlive/realtime-pipeline\";\n\nconst CAMPAIGN_ID = \"abc-123\";\n\ninitializeDataLayer({\n  tiltifyProxyUrl: \"https://tiltify-proxy.prod.experience.stjude.org\",\n  websocketUrl: \"wss://main.playlive.ws.api.experience.stjude.org\",\n});\n\nconst store: PipelineStoreApi = createPipelineStore({\n  initialCampaignIDs: [CAMPAIGN_ID],\n  initialOverlayName: \"donation-train\",\n  initialOverlayPath: \"/overlays/donation-train\",\n  initialTeamUserSlug: \"@playliver\",\n  initialDonationTrainsEnabled: true,\n});\n\nstore.getState().setOverlayInfo({\n  name: \"donation-train\",\n  teamUserSlug: \"@playliver\",\n  config: { theme: \"dark\" },\n  path: \"/overlays/donation-train\",\n});\n\n// Opt into the server-side slices this overlay actually needs. The second\n// argument re-sends the settings to the server on the next identify.\nstore.getState().updateTiltifySettings({ milestones: true, polls: false }, true);\n\nconst conn = createPipelineConnection({\n  store,\n  autoConnect: true, // url falls back to store.wsURL (from initializeDataLayer)\n  log: (msg, payload) => console.debug(\"[pipeline]\", msg, payload),\n});\n\nconn.on(\"error\", (event) => console.error(\"socket error\", event));\nconn.on(\"reconnect-stop\", (attempts) =>\n  console.error(`pipeline unreachable after ${attempts} attempts`),\n);\n\nfunction render(train: DonationTrain | undefined) {\n  if (!train || !train.trainVisible) return;\n  console.log(\n    `${train.trainStatus}: ${train.donationCount} donations, $${train.donationValue}`,\n  );\n}\n\nlet lastTrainId: string | null = null;\nstore.subscribe((s) => {\n  if (!s.hasReceivedInitialData) return;\n\n  const [train] = s.getDonationTrains(CAMPAIGN_ID, \"ACTIVE\");\n  if (train && train.id !== lastTrainId) {\n    lastTrainId = train.id;\n    console.log(\"new train started\", train.trainStart);\n  }\n  render(train);\n});\n\n// Operator-triggered resync (admin connections only — no-op otherwise).\nexport function forceResync() {\n  conn.refresh({ campaignID: CAMPAIGN_ID });\n}\n\nexport function teardown() {\n  conn.destroy();\n}\n```\n\n### Driving the store without a socket\n\n`processMessage` is the same pure dispatcher the connection uses, so tests,\nreplays, and simulators can feed the store directly:\n\n```ts\nimport { createPipelineStore } from \"@playlive/realtime-pipeline/store\";\nimport { processMessage } from \"@playlive/realtime-pipeline/connection\";\n\nconst store = createPipelineStore({ initialCampaignIDs: [\"c1\"] });\n\nprocessMessage(\n  store,\n  JSON.stringify({\n    type: \"public:direct:donation_updated\",\n    id: \"msg-1\",\n    sourceType: \"live\",\n    data: {\n      id: \"donation-1\",\n      campaign_id: \"c1\",\n      fundraising_event_id: null,\n      donor_name: \"Viewer1\",\n      amount: { value: \"25.00\", currency: \"USD\" },\n      completed_at: \"2025-01-01T00:00:00Z\",\n    },\n  }),\n);\n\nconsole.log(store.getState().getDonations(\"c1\").length); // 1\n\n// Unknown types are dropped, invalid JSON is dropped — never throws.\nprocessMessage(store, '{\"type\":\"someFutureMessage\"}');\nprocessMessage(store, \"not json\");\n```\n\n### Offline demo mode\n\nOverlays render demo content when the URL carries the demo slugs, with no\nnetwork access at all:\n\n```ts\nimport {\n  getDemoCampaign,\n  getDemoDonations,\n  isDemoMode,\n} from \"@playlive/realtime-pipeline/demo\";\n\n// /overlays/donation-bar/tiltify/@playliveDemoUser/playliveDemoCampaign\nconst [userOrTeamSlug, campaignSlug] = [\"playliveDemoUser\", \"playliveDemoCampaign\"];\n\nif (isDemoMode(userOrTeamSlug, campaignSlug)) {\n  const campaign = getDemoCampaign(false); // true → team campaign\n  const donations = getDemoDonations();\n  console.log(campaign.name, donations.length);\n} else {\n  // …boot the real pipeline connection\n}\n```\n\nThe demo slugs are exported as `DEMO_USER_SLUG` / `DEMO_TEAM_SLUG` and\n`DEMO_CAMPAIGN_SLUG` / `DEMO_TEAM_CAMPAIGN_SLUG`; `isDemoCampaignId` covers the\nmatching all-zero fixture UUIDs.\n\n### Injecting a mock socket in tests\n\n`WebSocketLike` / `WebSocketCtor` are exported precisely so tests never touch\nthe network — the package's own suite drives the state machine this way:\n\n```ts\nimport {\n  createPipelineConnection,\n  createPipelineStore,\n  type WebSocketCtor,\n  type WebSocketLike,\n} from \"@playlive/realtime-pipeline\";\n\nclass MockSocket implements WebSocketLike {\n  static instances: MockSocket[] = [];\n  static readonly CONNECTING = 0;\n  static readonly OPEN = 1;\n  static readonly CLOSING = 2;\n  static readonly CLOSED = 3;\n\n  readyState = 0;\n  onopen: ((ev: unknown) => void) | null = null;\n  onclose: ((ev: unknown) => void) | null = null;\n  onerror: ((ev: unknown) => void) | null = null;\n  onmessage: ((ev: { data: unknown }) => void) | null = null;\n  sent: string[] = [];\n\n  constructor(public readonly url: string) {\n    MockSocket.instances.push(this);\n  }\n  send(data: string) {\n    this.sent.push(data);\n  }\n  close() {\n    this.readyState = 3;\n    this.onclose?.({ code: 1000, reason: \"\", wasClean: true });\n  }\n}\n\nconst store = createPipelineStore({\n  initialURL: \"ws://test.local\",\n  initialCampaignIDs: [\"c1\"],\n});\nstore.getState().setOverlayInfo({\n  name: \"donation-bar\",\n  teamUserSlug: \"@user\",\n  config: {},\n  path: \"/overlays/donation-bar\",\n});\n\nconst conn = createPipelineConnection({\n  store,\n  webSocketCtor: MockSocket as unknown as WebSocketCtor,\n  autoConnect: true,\n});\n\nconst socket = MockSocket.instances[0]!;\nsocket.readyState = 1;\nsocket.onopen?.({});\n\nconst identify = JSON.parse(socket.sent[0]!);\nconsole.log(identify.action, identify.campaignIDs); // \"identify\" [\"c1\"]\n\nconn.destroy();\n```\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/realtime-pipeline/-/realtime-pipeline-0.3.2.tgz","shasum":"d2219ba3a29d2b0596adf15eb1a63c4328f1fc53","integrity":"sha512-g7iv1Z0Bum/sG40PIM9xjC/iC9FG2uyadLRIoTIdDatZy2g/86VAMLygkAudDWxMjqvawduKG1hVu8hopAr6qQ=="}}},"time":{"0.3.0":"2026-08-26T18:10:05.804Z","modified":"2026-08-26T20:07:56.694Z","0.1.0":"2026-08-26T18:15:41.232Z","0.1.1":"2026-08-26T18:15:42.598Z","0.2.0":"2026-08-26T18:15:43.284Z","0.2.1":"2026-08-26T18:15:43.863Z","0.2.2":"2026-08-26T18:15:44.511Z","0.2.3":"2026-08-26T18:15:45.037Z","0.2.4":"2026-08-26T18:15:45.560Z","0.2.5":"2026-08-26T18:15:46.189Z","0.2.6":"2026-08-26T18:15:46.780Z","0.2.7":"2026-08-26T18:15:47.364Z","0.2.8":"2026-08-26T18:15:48.372Z","0.2.9":"2026-08-26T18:15:49.062Z","0.2.10":"2026-08-26T18:15:49.753Z","0.2.11":"2026-08-26T18:15:50.520Z","0.3.1":"2026-08-26T19:43:42.743Z","0.3.2":"2026-08-26T20:07:56.694Z"}}