{"name":"@playlive/fundraiser-data","dist-tags":{"latest":"0.5.4"},"versions":{"0.5.2":{"name":"@playlive/fundraiser-data","version":"0.5.2","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.17","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.3.0"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-VKvhf8lDWHECfrf+snqoZ4x/VXFMQ7as5MzUNLX+fvxv96ipwHue1zim8MgW1BHX42Um9GEiT/WY1DCae6jKEg==","shasum":"648b84b5580bd0ac34dbdf95c240ea9b0d7af8e9","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTiltifyCurrentEvents`       | `./tiltify`    | Cause-level fundraising-event list (`GET public/causes/{id}/fundraising_events`, limit 100). Every year, published or not. Swallows errors → `[]`. |\n| `selectCurrentFundraisingEvents`  | `./projections` | Narrows a raw fundraising-event list to the in-flight Play Live season, newest first. Accepts `{ now }` for deterministic tests. |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `postTiltifyTestDonations`        | `./playlive`   | Fire a synthetic donation / batch through the core REST API (`POST /donations/tiltify/test`) so alerts, trains, timers and every WS subscriber react as if Tiltify delivered it. Accepts `adminApiKey` **or** `tiltifyOAuthToken`; demo campaigns need neither. Requires `twitchServiceUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.5.2.tgz","shasum":"648b84b5580bd0ac34dbdf95c240ea9b0d7af8e9","integrity":"sha512-VKvhf8lDWHECfrf+snqoZ4x/VXFMQ7as5MzUNLX+fvxv96ipwHue1zim8MgW1BHX42Um9GEiT/WY1DCae6jKEg=="}},"0.1.0":{"name":"@playlive/fundraiser-data","version":"0.1.0","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.1.1","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.1.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-v0wTLr/WXKblFXWDSb80Sp24tsEty0SrqqZQR7cOuoe5Mwwo7lYfabfS2fVvyb2RVLpZZtK5nCDzc1aJDyhteg==","shasum":"fc53043f5b608cb155e1a9e57157d59038ec14f6","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.1.0.tgz","shasum":"fc53043f5b608cb155e1a9e57157d59038ec14f6","integrity":"sha512-v0wTLr/WXKblFXWDSb80Sp24tsEty0SrqqZQR7cOuoe5Mwwo7lYfabfS2fVvyb2RVLpZZtK5nCDzc1aJDyhteg=="}},"0.1.1":{"name":"@playlive/fundraiser-data","version":"0.1.1","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.8","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-f4AnExH6LTI9DtiFM9SiHz6bIQ0htDBIF5V/6zlmxJ+2177kyOTImSBE+6t8KnIQzpnfEp5xu3IuePIaD2PNFw==","shasum":"e842eedef81d53711034c0b637327a82fa26a4e9","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.1.1.tgz","shasum":"e842eedef81d53711034c0b637327a82fa26a4e9","integrity":"sha512-f4AnExH6LTI9DtiFM9SiHz6bIQ0htDBIF5V/6zlmxJ+2177kyOTImSBE+6t8KnIQzpnfEp5xu3IuePIaD2PNFw=="}},"0.1.2":{"name":"@playlive/fundraiser-data","version":"0.1.2","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.8","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-RpSOVr0Rf/uE/ERS01782Ym5ja/sc37zBZETx9DJyhpAv1VpZDiUAnB1W1raabe4pHPYAa8NqDmj9Q9ZUUOM3Q==","shasum":"a2feea92011a325632e4e654d4d297b36ec1a988","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.1.2.tgz","shasum":"a2feea92011a325632e4e654d4d297b36ec1a988","integrity":"sha512-RpSOVr0Rf/uE/ERS01782Ym5ja/sc37zBZETx9DJyhpAv1VpZDiUAnB1W1raabe4pHPYAa8NqDmj9Q9ZUUOM3Q=="}},"0.1.3":{"name":"@playlive/fundraiser-data","version":"0.1.3","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.8","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-dpBgKwP4OBrdTOIr7jY3q11riRF55ti0xABJgq0GR1WPYuoThRN/AEGcTfK1OYPYBKPYwz8hnFLQGQOIHuk2jw==","shasum":"969d67d2722a591f0a28e165384a752acb800eb0","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.1.3.tgz","shasum":"969d67d2722a591f0a28e165384a752acb800eb0","integrity":"sha512-dpBgKwP4OBrdTOIr7jY3q11riRF55ti0xABJgq0GR1WPYuoThRN/AEGcTfK1OYPYBKPYwz8hnFLQGQOIHuk2jw=="}},"0.1.4":{"name":"@playlive/fundraiser-data","version":"0.1.4","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.10","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-4xYpKUPgxPcwHVe41du5xwSoCNbTDOy+8o4xnhywsIUPvVk4z2UDOZozkBHk88/KgoER1tYlCXbeK9P69uRI3w==","shasum":"95bc9b2a41b98bb43cd4c5a440d90bc77cd90d3a","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.1.4.tgz","shasum":"95bc9b2a41b98bb43cd4c5a440d90bc77cd90d3a","integrity":"sha512-4xYpKUPgxPcwHVe41du5xwSoCNbTDOy+8o4xnhywsIUPvVk4z2UDOZozkBHk88/KgoER1tYlCXbeK9P69uRI3w=="}},"0.1.5":{"name":"@playlive/fundraiser-data","version":"0.1.5","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.10","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-7vQjmjPJEefwGJOcDUSYA6BEqtl3zNyjBbpbnXqhyFjVbEOzNNPOLqEEpwmUb0Kn8NJVQ/7mWOaxo2dHTOBwAQ==","shasum":"64e8a50647bdbdaeed9bbc296af05530e7fa3da1","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.1.5.tgz","shasum":"64e8a50647bdbdaeed9bbc296af05530e7fa3da1","integrity":"sha512-7vQjmjPJEefwGJOcDUSYA6BEqtl3zNyjBbpbnXqhyFjVbEOzNNPOLqEEpwmUb0Kn8NJVQ/7mWOaxo2dHTOBwAQ=="}},"0.2.0":{"name":"@playlive/fundraiser-data","version":"0.2.0","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.2"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-anMKz8MHL/peTw+DBGR2rbM16ehwMCpiH5KBRAO4X82hIUtbAnvNX3uBXyg5RjybkKLnWZCjXyFr5/ANVWQZsg==","shasum":"81d1ae8b8babb7a2626fa66afcba8593d13462a1","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.2.0.tgz","shasum":"81d1ae8b8babb7a2626fa66afcba8593d13462a1","integrity":"sha512-anMKz8MHL/peTw+DBGR2rbM16ehwMCpiH5KBRAO4X82hIUtbAnvNX3uBXyg5RjybkKLnWZCjXyFr5/ANVWQZsg=="}},"0.2.1":{"name":"@playlive/fundraiser-data","version":"0.2.1","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.4"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-0tOL1HDKT5Kyeef9J2OecDUhvhdg6U7lUVixOpx+7XK/jJNVcHK6+f3CHnuTtdHGSilV8rxVEanj5/l36+YmiA==","shasum":"02418f435a0e1aca0d731e7827b4c9737712e52f","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: \"https://api.experience.stjude.org/tiltify\",\n  twitchServiceUrl: \"https://api.experience.stjude.org/twitch\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.prod.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime.api.prod.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.prod.experience.stjude.org\",\n});\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions` |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                 |\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.2.1.tgz","shasum":"02418f435a0e1aca0d731e7827b4c9737712e52f","integrity":"sha512-0tOL1HDKT5Kyeef9J2OecDUhvhdg6U7lUVixOpx+7XK/jJNVcHK6+f3CHnuTtdHGSilV8rxVEanj5/l36+YmiA=="}},"0.2.2":{"name":"@playlive/fundraiser-data","version":"0.2.2","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.4"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-0MurgLomrq/tngU5uDO6XCN5ajpOgQkbgRjCknrlA65ltRFVYDMDb33fXab+XhfhhQGcSABzcW08002DsaJp9w==","shasum":"5c244ba5473fd6771b8bd20a172e374d43f44119","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions` |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.2.2.tgz","shasum":"5c244ba5473fd6771b8bd20a172e374d43f44119","integrity":"sha512-0MurgLomrq/tngU5uDO6XCN5ajpOgQkbgRjCknrlA65ltRFVYDMDb33fXab+XhfhhQGcSABzcW08002DsaJp9w=="}},"0.2.3":{"name":"@playlive/fundraiser-data","version":"0.2.3","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.4"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-t0fXf6Gc1zFH/irUkfclbhSAazB1jq/ZesZirDcZMgQ2b++/mBebwLJthLnv3KICxxoaP4N5vPkoAF9idIQAKg==","shasum":"b3948536cb19eac8625c7544e51e9c7bd910585b","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions` |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.2.3.tgz","shasum":"b3948536cb19eac8625c7544e51e9c7bd910585b","integrity":"sha512-t0fXf6Gc1zFH/irUkfclbhSAazB1jq/ZesZirDcZMgQ2b++/mBebwLJthLnv3KICxxoaP4N5vPkoAF9idIQAKg=="}},"0.2.4":{"name":"@playlive/fundraiser-data","version":"0.2.4","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.4"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-5bYIscdRRbDwqYeCsaH783N6j6e/X0QKe4svhcQiC4RhtwxoHYLIpGQAH6c/NVRItVY7Spq9WwAjUnsZ7JgX+w==","shasum":"e97d7133351c03a30186d01f6b3c5cc91e931773","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions` |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.2.4.tgz","shasum":"e97d7133351c03a30186d01f6b3c5cc91e931773","integrity":"sha512-5bYIscdRRbDwqYeCsaH783N6j6e/X0QKe4svhcQiC4RhtwxoHYLIpGQAH6c/NVRItVY7Spq9WwAjUnsZ7JgX+w=="}},"0.3.0":{"name":"@playlive/fundraiser-data","version":"0.3.0","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-hZx2mjI+CkqxDnaUNeHfYrT/lg1XAU5OU3aZ3qoQCGl2ZnoSoiHPRmm0sJF0NqFNNCmVY/oj3L2LiJyDKZDoXg==","shasum":"cfa86d2e436551babc50c5e6dcabb85683b07716","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions` |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.3.0.tgz","shasum":"cfa86d2e436551babc50c5e6dcabb85683b07716","integrity":"sha512-hZx2mjI+CkqxDnaUNeHfYrT/lg1XAU5OU3aZ3qoQCGl2ZnoSoiHPRmm0sJF0NqFNNCmVY/oj3L2LiJyDKZDoXg=="}},"0.3.1":{"name":"@playlive/fundraiser-data","version":"0.3.1","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-UPxoCeONeOqog0v75fqpga0KYComQCQk6biB86V4sotqe1UQfdilC+7kkhmeEHBJQ+VTETCZEY4QigmPNZwCXw==","shasum":"4ed86478451c3368716b2c6e26f99e56d4f379ba","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.3.1.tgz","shasum":"4ed86478451c3368716b2c6e26f99e56d4f379ba","integrity":"sha512-UPxoCeONeOqog0v75fqpga0KYComQCQk6biB86V4sotqe1UQfdilC+7kkhmeEHBJQ+VTETCZEY4QigmPNZwCXw=="}},"0.3.2":{"name":"@playlive/fundraiser-data","version":"0.3.2","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-YrGgV4lc3SjaQ3lnDAQNW4ewwMR2dvFuzve+2ZWVcQ14Ft+QpQ+XvsDDcMmw7h9xvD5KT55FeU3uVmlO0ESXDg==","shasum":"e43447f4f376de956673fd12527cfd07180027c9","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.3.2.tgz","shasum":"e43447f4f376de956673fd12527cfd07180027c9","integrity":"sha512-YrGgV4lc3SjaQ3lnDAQNW4ewwMR2dvFuzve+2ZWVcQ14Ft+QpQ+XvsDDcMmw7h9xvD5KT55FeU3uVmlO0ESXDg=="}},"0.3.3":{"name":"@playlive/fundraiser-data","version":"0.3.3","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-k3WFCtHf42i6B2+7gmYkujmd2Ng/v1M86+xhs6jxFUAGRPqXL0+Ayii+nbuFeN68hTsSgTMNStSGehBHvEusLQ==","shasum":"37206eeabc68204eb9b0f0155a210b1e6339722c","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.3.3.tgz","shasum":"37206eeabc68204eb9b0f0155a210b1e6339722c","integrity":"sha512-k3WFCtHf42i6B2+7gmYkujmd2Ng/v1M86+xhs6jxFUAGRPqXL0+Ayii+nbuFeN68hTsSgTMNStSGehBHvEusLQ=="}},"0.3.4":{"name":"@playlive/fundraiser-data","version":"0.3.4","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-yh1wPZWzlFqnxZrMdTa8Y4BxHUDcqPW5eX06Ft1gMpm3XECFSlXPh/udLtYlknQHOY3KnEDp68rz+8SNHeVoRg==","shasum":"e871baa2209a48395460722837985b6e015b6191","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.3.4.tgz","shasum":"e871baa2209a48395460722837985b6e015b6191","integrity":"sha512-yh1wPZWzlFqnxZrMdTa8Y4BxHUDcqPW5eX06Ft1gMpm3XECFSlXPh/udLtYlknQHOY3KnEDp68rz+8SNHeVoRg=="}},"0.4.0":{"name":"@playlive/fundraiser-data","version":"0.4.0","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-UuDux9SL8NqCe7Q60xsR/iLqJZs/ges36w+xtJ700Y44eFIn0CfH1uGv6C+DS+HvvAZTqcPDR2q84t7o3nUyxA==","shasum":"8fb3cc58f6698696881eed5f12b9f34f25602f5f","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.4.0.tgz","shasum":"8fb3cc58f6698696881eed5f12b9f34f25602f5f","integrity":"sha512-UuDux9SL8NqCe7Q60xsR/iLqJZs/ges36w+xtJ700Y44eFIn0CfH1uGv6C+DS+HvvAZTqcPDR2q84t7o3nUyxA=="}},"0.4.1":{"name":"@playlive/fundraiser-data","version":"0.4.1","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.5"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-gglTvbC+MVM9EDoCBRtVFe/+PokhcZoihY+LWDGsIDQs2MeFaDlD4InRfLgaag/OJ1gffrrfqNUcW+aCZzVXnQ==","shasum":"f1395a5a0d91e525758f75a33d19b7abe0644799","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.4.1.tgz","shasum":"f1395a5a0d91e525758f75a33d19b7abe0644799","integrity":"sha512-gglTvbC+MVM9EDoCBRtVFe/+PokhcZoihY+LWDGsIDQs2MeFaDlD4InRfLgaag/OJ1gffrrfqNUcW+aCZzVXnQ=="}},"0.4.2":{"name":"@playlive/fundraiser-data","version":"0.4.2","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.11","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.7"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-RWsb9LGV3ZWljxKOl35z94OUBbFn1dNCcwbxrcQSLWtjAQ1R5F1XIhu6KWWDodm1eo7z2XCW6ivKr2Iz6mdVkw==","shasum":"7508bc4de3322013af6464ce72794a836d0a1423","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.4.2.tgz","shasum":"7508bc4de3322013af6464ce72794a836d0a1423","integrity":"sha512-RWsb9LGV3ZWljxKOl35z94OUBbFn1dNCcwbxrcQSLWtjAQ1R5F1XIhu6KWWDodm1eo7z2XCW6ivKr2Iz6mdVkw=="}},"0.4.3":{"name":"@playlive/fundraiser-data","version":"0.4.3","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.13","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.8"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-B7jEE1Cg/rFrsDNIDM0tkqHS32c0Fvt6b2RZkDL9ovrZ7VbLWgrx+YEoqQaDINrvOjhiG7aWfdWd/HIQDP/pHw==","shasum":"d7e8620e12d6568e845ce5aa5c4d6032422200e6","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.4.3.tgz","shasum":"d7e8620e12d6568e845ce5aa5c4d6032422200e6","integrity":"sha512-B7jEE1Cg/rFrsDNIDM0tkqHS32c0Fvt6b2RZkDL9ovrZ7VbLWgrx+YEoqQaDINrvOjhiG7aWfdWd/HIQDP/pHw=="}},"0.5.0":{"name":"@playlive/fundraiser-data","version":"0.5.0","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.13","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.10"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-Po/bwKDzFKf51fmEx61t7kYGQiLFQer8AregDm8refJ/bOw6626fzMw4ShJomMULBw3MisCblQXkLzhy5ybrLw==","shasum":"0283af011ebeea1e73ac8d5211b88c83c4f6d0bc","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTiltifyCurrentEvents`       | `./tiltify`    | Cause-level fundraising-event list (`GET public/causes/{id}/fundraising_events`, limit 100). Every year, published or not. Swallows errors → `[]`. |\n| `selectCurrentFundraisingEvents`  | `./projections` | Narrows a raw fundraising-event list to the in-flight Play Live season, newest first. Accepts `{ now }` for deterministic tests. |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `postTiltifyTestDonations`        | `./playlive`   | Fire a synthetic donation / batch through the core REST API (`POST /donations/tiltify/test`) so alerts, trains, timers and every WS subscriber react as if Tiltify delivered it. Accepts `adminApiKey` **or** `tiltifyOAuthToken`; demo campaigns need neither. Requires `twitchServiceUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.5.0.tgz","shasum":"0283af011ebeea1e73ac8d5211b88c83c4f6d0bc","integrity":"sha512-Po/bwKDzFKf51fmEx61t7kYGQiLFQer8AregDm8refJ/bOw6626fzMw4ShJomMULBw3MisCblQXkLzhy5ybrLw=="}},"0.5.1":{"name":"@playlive/fundraiser-data","version":"0.5.1","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.17","@playlive/twitch-charity":"^0.1.0","@playlive/realtime-pipeline":"^0.2.10"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-5PdCxs+W3uux3j/VbZatLcEAcnxGIHqBi4ptb1DI+jOSmyZtx287cZOfcUSlyFt2EmJdA9Cv712IUlZYOUTF6g==","shasum":"d5887e6f1b90a0d1bf511a4ef77006ac36b6f1ff","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\nbun add @playlive/tiltify-core              # required peer\nbun add @playlive/realtime-pipeline         # optional peer — only needed for demo fixtures\n```\n\n`@playlive/tiltify-core` is a **peer dependency** (jose-style — consumer\nbrings their own copy so wire types stay in lockstep across packages).\n`@playlive/realtime-pipeline` is an **optional peer**, used solely for\nthe demo fixture provider — see \"Demo mode\" below.\n\nNo runtime deps beyond those two peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  fetchCampaign,\n  fetchMilestones,\n  createDonationsFetcher,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl:  \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl:    \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl:    \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports everything below.                                |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `setDemoProvider`, `resetConfig`, …                |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `createTiltifyLeaderboardFetcher`, `fetchTiltifyMilestones`, … |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `convertTwitchToTiltifyCampaign`, `TwitchApiError`, … |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `fetchDonorSpotlight` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel` + `DONOR_LEVEL_THRESHOLDS` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / etc.   |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode` predicate + slug / ID constants (no fixtures — see Demo mode).  |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, …                |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen preset objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent.       |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`.      |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.        |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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| `configure`                       | `./config`     | Set proxy URLs + cause ID. Idempotent.                               |\n| `setDemoProvider`                 | `./config`     | Inject demo fixtures (typically from `@playlive/realtime-pipeline/demo`). |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher.                           |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory.                              |\n| `fetchMilestones` / `Rewards` / `Polls` / `Targets` | `./unified` | Twitch returns `[]` for all four (unsupported).                  |\n| `fetchUser` / `Team` / `FundraisingEvent` / `Cause` / `EventCampaigns` | `./unified` | Twitch returns `null` / `[]` (unsupported). |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers.                                    |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard.               |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages`.                              |\n| `fetchTiltifyCurrentEvents`       | `./tiltify`    | Cause-level fundraising-event list (`GET public/causes/{id}/fundraising_events`, limit 100). Every year, published or not. Swallows errors → `[]`. |\n| `selectCurrentFundraisingEvents`  | `./projections` | Narrows a raw fundraising-event list to the in-flight Play Live season, newest first. Accepts `{ now }` for deterministic tests. |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + shape adapters.                               |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy.                             |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `postTiltifyTestDonations`        | `./playlive`   | Fire a synthetic donation / batch through the core REST API (`POST /donations/tiltify/test`) so alerts, trains, timers and every WS subscriber react as if Tiltify delivered it. Accepts `adminApiKey` **or** `tiltifyOAuthToken`; demo campaigns need neither. Requires `twitchServiceUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `isDemoMode`, `DEMO_*`            | `./demo`       | Slug predicate + identifier constants (zero fixtures inlined).       |\n| `PACKAGE_NAME`                    | `./`           | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | `./`           | Twitch Extension URL disclosure list.                                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n`@playlive/tiltify-core`) live at [`specs/tiltify/`](../../specs/tiltify/).\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the shapes used here are inlined and version-pinned by tests.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied `tiltifyProxyUrl` +\n`twitchServiceUrl` passed to `configure()`. The `KNOWN_URLS` export is\ntherefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add the proxy URLs you pass to `configure()` to\nits own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table.\n\n## Examples\n\nEnd-to-end usage scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) land in `examples/` once `dev/greenroom`\n(phase 8) is wired up as the harness.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.5.1.tgz","shasum":"d5887e6f1b90a0d1bf511a4ef77006ac36b6f1ff","integrity":"sha512-5PdCxs+W3uux3j/VbZatLcEAcnxGIHqBi4ptb1DI+jOSmyZtx287cZOfcUSlyFt2EmJdA9Cv712IUlZYOUTF6g=="}},"0.5.3":{"name":"@playlive/fundraiser-data","version":"0.5.3","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.18","@playlive/twitch-charity":"^0.1.1","@playlive/realtime-pipeline":"^0.3.1"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-48DO3yMaRqAo1F85yOnsE3XkXrmbxphOTK766RrOnUHM07qyd5u1ILGaKRNfVeGUbPSyYmjXfxXUXTF5qF+DVg==","shasum":"0d51855b8c0b16e9abf0c5cc2490a0dac454a75a","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\n```\n\nTwo of the three peers are **required** and are installed automatically\nby npm 7+ / Bun, so the one-liner above already pulls them in:\n\n| Peer                                                  | Required? | Why                                                                            |\n| ----------------------------------------------------- | --------- | ------------------------------------------------------------------------------ |\n| [`@playlive/tiltify-core`](../tiltify/core/)          | **yes**   | Every Tiltify fetcher drives the `tiltify` singleton and re-exports its types.  |\n| [`@playlive/twitch-charity`](../twitch/charity/)      | **yes**   | Owns the Twitch charity wire shapes, fetchers, and Tiltify converters.          |\n| [`@playlive/realtime-pipeline`](../realtime-pipeline/) | optional  | Demo fixtures only (`peerDependenciesMeta.optional`). See [Demo mode](#demo-mode). |\n\nTo pin them explicitly (recommended for apps that also import those\npackages directly, so a single version is hoisted):\n\n```bash\nbun add @playlive/fundraiser-data @playlive/tiltify-core @playlive/twitch-charity\nbun add @playlive/realtime-pipeline   # optional — demo fixtures only\n```\n\nPeers are declared jose-style so wire types stay in lockstep across\npackages. No runtime dependencies beyond the peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  createDonationsFetcher,\n  fetchCampaign,\n  fetchMilestones,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // Tiltify proxy is deployed outside UDP\n  twitchServiceUrl: \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl: \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl: \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four UDP URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n`configure()` must run before any `fetch*()` call — `getConfig()` throws\n`\"@playlive/fundraiser-data not configured\"` otherwise, so a misordered\nboot sequence surfaces immediately instead of silently 404ing.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports every subpath below, plus `PACKAGE_NAME` + `KNOWN_URLS`. |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `isConfigured`, `resetConfig`, `setDemoProvider`, `getDemoProvider`, `resetDemoProvider`, `DEFAULT_CAUSE_ID`, `DEFAULT_CONFIG` + the `FundraiserDataConfig` / `DemoProvider` types. |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyFlattenedDonations`, `fetchTiltifyMilestones`, `fetchTiltifyRewards`, `fetchTiltifyPolls`, `fetchTiltifyTargets`, `fetchTiltifySchedule`, `fetchTiltifyUser`, `fetchTiltifyTeam`, `fetchTiltifyFundraisingEvent`, `fetchTiltifyFundraisingEventMilestones`, `fetchTiltifyCause`, `fetchTiltifyEventCampaigns`, `fetchTiltifyCurrentEvents`, `fetchTiltifyUserCampaigns`, `fetchTiltifyUserAndTeamCampaigns`, `createTiltifyLeaderboardFetcher` |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `fetchTwitchCampaignDonations`, `createTwitchDonationsFetcher`, `convertTwitchToTiltifyCampaign`, `convertTwitchToTiltifyDonation`, `TwitchApiError` |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `bucketGiftsThatGiveGoal`, `GIFTS_THAT_GIVE_MILESTONE_GOALS`, `fetchDonorSpotlight`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `postTiltifyTestDonations` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset`, `requireDonationTrainApiUrl` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel`, `DONOR_LEVEL_THRESHOLDS`, `selectCurrentFundraisingEvents` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`, `buildPreset`, `ENV_URLS`, `GENERATED_AT`. URLs sourced from UDP CloudFormation outputs; refresh with `bun run sync-environments` at the workspace root. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / `fetchMilestones` / `fetchRewards` / `fetchPolls` / `fetchTargets` / `fetchSchedule` / `fetchUser` / `fetchTeam` / `fetchFundraisingEvent` / `fetchFundraisingEventMilestones` / `fetchCause` / `fetchEventCampaigns`. |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode`, `isDemoCampaignId`, `isDemoFundraisingEventId`, `stripSigil` + the `DEMO_*` slug / ID constants (no fixtures — see [Demo mode](#demo-mode)). |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, `TiltifyPaginationMetadata`, `TwitchPaginationMetadata`. |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live UDP\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are sourced\nfrom the `ApiDomainName` CloudFormation output of each nested UDP\nstack — so the presets track the deployed truth, not a hand-typed\ncopy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // Tiltify proxy lives outside UDP — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen `FundraiserEnvPreset` objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent. |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`. Throws on an unknown `env`. |\n| `buildPreset(env)`                    | Pure preset builder behind the three frozen constants. Exported for tests.                        |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.           |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last CloudFormation sync.                                               |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\n**Refreshing the URL table.** Run at the workspace root:\n\n```bash\nbun run sync-environments              # fetch + write + health-check\nbun run sync-environments:check        # CI drift check (no writes, still health-checks)\nbun run sync-environments:health       # health check only — no AWS calls\n```\n\nThe script queries the three UDP root stacks (`udp-dev`,\n`udp-lambda-qa`, `udp-lambda-prod`) via CloudFormation\n`DescribeStacks`, reads each nested stack's `ApiDomainName` output,\nand rewrites `packages/fundraiser-data/src/environments/generated.ts`.\nOverride stack names via `UDP_ROOT_STACK_{DEV,QA,PROD}` env vars if\nthe account topology changes.\n\n**Health check.** After the URLs are written, the script fans out\n`GET <url>/health` against every populated URL (three envs × four\nservices = twelve requests, all in parallel) and exits non-zero if\nany returns non-2xx or times out (default 10 s per request; override\nwith `--timeout=<ms>`). Add `--skip-health` to skip the round.\nUse `--health-only` to run the round against the currently-committed\ntable without touching AWS.\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\n### Configuration (`./config`)\n\n| Export                | Kind      | Notes                                                                     |\n| --------------------- | --------- | ------------------------------------------------------------------------- |\n| `configure`           | function  | `configure(config: FundraiserDataConfig): void`. Idempotent — a later call replaces the previous config. |\n| `getConfig`           | function  | Returns `Required<FundraiserDataConfig>`. **Throws** when called before `configure()`. |\n| `isConfigured`        | function  | Non-throwing `boolean` probe.                                             |\n| `resetConfig`         | function  | Wipes config. Tests should call this in `afterEach`.                       |\n| `setDemoProvider`     | function  | Inject demo fixtures (typically the whole `@playlive/realtime-pipeline/demo` namespace). Pass `null` to detach. |\n| `getDemoProvider` / `resetDemoProvider` | function | Read back / clear the registered provider.               |\n| `FundraiserDataConfig` | interface | `tiltifyProxyUrl` (required) + `twitchServiceUrl`, `causeId`, `scheduleApiUrl`, `lifetimeApiUrl`, `leaderboardApiUrl`, `donorSpotlightApiUrl`, `donationTrainApiUrl`. |\n| `DemoProvider`        | interface | All-optional `getDemo*` methods; unimplemented ones fall back to `null` / `[]`. |\n| `DEFAULT_CAUSE_ID`    | const     | St. Jude cause UUID — the default for `config.causeId`.                    |\n| `DEFAULT_CONFIG`      | const     | Defaults merged under the consumer config (every optional URL defaults to `\"\"`). |\n\nState is anchored on a `Symbol.for()` slot on `globalThis`, so duplicate\nmodule copies produced by a bundler's `optimizeDeps` pre-bundling still\nresolve the same singleton.\n\n### Fetchers\n\n| Export                            | Source         | Notes                                                                |\n| --------------------------------- | -------------- | -------------------------------------------------------------------- |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher. Twitch payloads are projected into the Tiltify shape. |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory. Overloaded: a `\"tiltify\"` literal yields a string-cursor closure (`metadata.after`), `\"twitch\"` a numeric-page one (`metadata.nextPage`). |\n| `fetchMilestones` / `fetchRewards` / `fetchPolls` / `fetchTargets` / `fetchSchedule` | `./unified` | Twitch returns `[]` for all five (unsupported).             |\n| `fetchUser` / `fetchTeam` / `fetchFundraisingEvent` / `fetchCause` | `./unified` | Twitch returns `null` (unsupported).                     |\n| `fetchEventCampaigns` / `fetchFundraisingEventMilestones` | `./unified` | Twitch returns `[]` (unsupported).                   |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers. `fetchTiltifyCampaign` **throws** `\"Campaign not found\"` when neither `id` nor `(teamUserSlug, slug)` resolves; the collection fetchers swallow errors → `[]`. |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard. |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages` (default 100 pages × 100 rows). |\n| `fetchTiltifyCurrentEvents`       | `./tiltify`    | Cause-level fundraising-event list (`GET public/causes/{id}/fundraising_events`, limit 100). Every year, published or not. Swallows errors → `[]`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + pure shape adapters (re-exported from `@playlive/twitch-charity`, with `causeId` injected from the singleton config). |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy. **Deprecated alias** of `TwitchCharityApiError` — same class, so `instanceof` matches either name. |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchGiftsThatGiveMilestones`    | `./playlive`   | Ordered gifts-that-give rows for a goal (`GiftsThatGiveMilestoneGoal` literal or raw number). Requires `lifetimeApiUrl`. |\n| `bucketGiftsThatGiveGoal`         | `./playlive`   | Snap an arbitrary goal amount **down** to the highest `GIFTS_THAT_GIVE_MILESTONE_GOALS` tier it clears (`1200 → \"1000\"`). Returns `null` below the `$100` floor. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). Returns `MonetaryLeaderboardEntry[]`. |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `postTiltifyTestDonations`        | `./playlive`   | Fire a synthetic donation / batch through the core REST API (`POST /donations/tiltify/test`) so alerts, trains, timers and every WS subscriber react as if Tiltify delivered it. Accepts `adminApiKey` **or** `tiltifyOAuthToken`; demo campaigns need neither. Requires `twitchServiceUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n\n### Projections (`./projections`)\n\nPure, network-free functions over the Tiltify domain types — safe to\ncall from a Node warmer, a Lambda, or a React render.\n\n| Export                                  | Kind     | Notes                                                                  |\n| --------------------------------------- | -------- | ---------------------------------------------------------------------- |\n| `extractCampaignAmounts`                | function | `CampaignLike \\| null` → `{ totalAmount, currentAmount, goalAmount, originalGoalAmount, supportingAmount }`, all defensively parsed to numbers (`0` on missing / NaN). |\n| `extractCampaignFundraisingEventAmounts` | function | Same idea across a campaign + its parent fundraising event. `{ forceCampaignGoal: true }` pins `overallGoalAmount` to the campaign's own goal instead of the umbrella event goal. |\n| `flattenDonationPages`                  | function | `{ pages: [{ data }] }` → one sorted `TiltifyDonation[]`. Newest first; `{ flipSorting: true }` for oldest first. Non-donation rows are dropped. |\n| `getDonorLevel`                         | function | `number \\| string` → `DonorLevel`. Truncates before comparison, so `24.99 → \"grey\"`. |\n| `DONOR_LEVEL_THRESHOLDS`                | const    | Frozen ascending ladder: bronze 25 / silver 50 / gold 75 / platinum 100. |\n| `selectCurrentFundraisingEvents`        | function | Narrows a raw fundraising-event list to the in-flight Play Live season, newest first. Accepts `{ now }` for deterministic tests. |\n\n### Demo + package metadata\n\n| Export                            | Kind     | Notes                                                                |\n| --------------------------------- | -------- | -------------------------------------------------------------------- |\n| `isDemoMode(userOrTeamSlug, campaignSlug)` | function | Predicate over an already-sigil-stripped slug pair.        |\n| `isDemoCampaignId` / `isDemoFundraisingEventId` | function | UUID-sentinel predicates.                          |\n| `stripSigil`                      | function | Drops a leading `@` (user) or `+` (team) from a slug; type-preserving overloads. |\n| `DEMO_USER_SLUG`, `DEMO_TEAM_SLUG`, `DEMO_CAMPAIGN_SLUG`, `DEMO_TEAM_CAMPAIGN_SLUG`, `DEMO_CAMPAIGN_ID`, `DEMO_TEAM_CAMPAIGN_ID`, `DEMO_FUNDRAISING_EVENT_ID` | const | Identifier constants (zero fixtures inlined). |\n| `PACKAGE_NAME`                    | const    | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | const    | Twitch Extension URL disclosure list (frozen, empty).                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { fetchCampaign, setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nThe `@` / `+` sigil is optional — `fetchTiltifyCampaign` runs\n`stripSigil()` before matching, so `\"@playliveDemoUser\"` and\n`\"playliveDemoUser\"` behave identically.\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration. Full walkthrough:\n[`docs/demo-mode.md`](../../docs/demo-mode.md).\n\n## Upstream spec\n\nThe Tiltify v5 REST OpenAPI snapshots (consumed transitively via\n[`@playlive/tiltify-core`](../tiltify/core/)) live at\n[`specs/tiltify/`](../../specs/tiltify/). Re-sync via the\n`/charity:sync` skill or `bun run sync-charity-specs` at the workspace\nroot; see\n[`docs/charity-spec-sync.md`](../../docs/charity-spec-sync.md).\n\nThe Twitch Charity proxy is internal to Play Live and has no public\nspec; the canonical shapes live in\n[`@playlive/twitch-charity`](../twitch/charity/) and are version-pinned\nby tests. The Play Live first-party services (`/playlive`,\n`/donation-trains`) are documented by the Swagger UI each UDP service\npublishes at `<url>/docs`.\n\nWhen the Twitch service evolves, update `src/twitch/index.ts` then\nregenerate the badge with `bun run coverage`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied URLs passed to\n`configure()`. The `KNOWN_URLS` export is therefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add every URL it passes to `configure()`\n(`tiltifyProxyUrl`, `twitchServiceUrl`, `scheduleApiUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `donorSpotlightApiUrl`,\n`donationTrainApiUrl`) — or the corresponding `ENV_URLS` row when using\na preset — to its own Extension URL disclosure. See\n[`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\nSee the root [`MIGRATION.md`](../../MIGRATION.md) for the full\nper-symbol table and\n[`docs/overlay-data-layer-migration.md`](../../docs/overlay-data-layer-migration.md)\nfor the step-by-step port.\n\n## Examples\n\n### Boot an overlay data layer end-to-end\n\nConfig → campaign → children in parallel → pure projections. This is\nthe shape every Play Live overlay uses; the React flavour of the same\nflow lives in [`@playlive/react-data`](../react-data/).\n\n```ts\nimport {\n  configure,\n  DEMO_CAMPAIGN_SLUG,\n  DEMO_USER_SLUG,\n  extractCampaignAmounts,\n  fetchMilestones,\n  fetchTiltifyCampaign,\n  fetchTiltifyFlattenedDonations,\n  getConfigForEnv,\n  getDonorLevel,\n  setDemoProvider,\n} from \"@playlive/fundraiser-data\";\nimport type { TiltifyDonation, TiltifyMilestone } from \"@playlive/tiltify-core\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    tiltifyProxyUrl: \"https://tiltify-proxy.prod.experience.stjude.org\",\n  }),\n);\n\n// Optional: makes the demo slugs below resolve without any network.\nsetDemoProvider(demo);\n\nexport interface OverlaySnapshot {\n  campaignName: string;\n  raised: number;\n  goal: number;\n  percent: number;\n  nextMilestone: TiltifyMilestone | undefined;\n  topDonors: Array<{ name: string; amount: string; level: string }>;\n}\n\nexport async function loadOverlay(\n  teamUserSlug: string,\n  campaignSlug: string,\n): Promise<OverlaySnapshot | null> {\n  let campaign: Awaited<ReturnType<typeof fetchTiltifyCampaign>>;\n  try {\n    // Throws \"Campaign not found\" when the slug pair resolves nothing;\n    // resolves `null` when the campaign belongs to another cause.\n    campaign = await fetchTiltifyCampaign({ teamUserSlug, slug: campaignSlug });\n  } catch (error) {\n    console.error(\"campaign lookup failed\", error);\n    return null;\n  }\n  if (!campaign) return null;\n\n  // Milestones swallow their own errors → `[]`; donations are capped to\n  // one page so a long-running campaign doesn't walk 10k rows per tick.\n  const [milestones, donations] = await Promise.all([\n    fetchMilestones({ charityType: \"tiltify\", campaignId: campaign.id }),\n    fetchTiltifyFlattenedDonations({\n      campaignId: campaign.id,\n      count: 50,\n      maxPages: 1,\n    }),\n  ]);\n\n  const { currentAmount, goalAmount } = extractCampaignAmounts(campaign);\n\n  const nextMilestone = milestones\n    .filter((m) => m.active)\n    .sort(\n      (a, b) => Number.parseFloat(a.amount.value) - Number.parseFloat(b.amount.value),\n    )\n    .find((m) => Number.parseFloat(m.amount.value) > currentAmount);\n\n  const topDonors = [...donations]\n    .sort(\n      (a: TiltifyDonation, b: TiltifyDonation) =>\n        Number.parseFloat(b.amount.value) - Number.parseFloat(a.amount.value),\n    )\n    .slice(0, 5)\n    .map((d) => ({\n      name: d.donor_name,\n      amount: d.amount.value,\n      level: getDonorLevel(d.amount.value), // \"platinum\" | … | \"grey\"\n    }));\n\n  return {\n    campaignName: campaign.name,\n    raised: currentAmount,\n    goal: goalAmount,\n    percent: goalAmount > 0 ? (currentAmount / goalAmount) * 100 : 0,\n    nextMilestone,\n    topDonors,\n  };\n}\n\n// Demo slugs resolve entirely from the injected fixtures.\nawait loadOverlay(DEMO_USER_SLUG, DEMO_CAMPAIGN_SLUG);\n```\n\n### Walk the donation cursor manually\n\n`createDonationsFetcher` is overloaded on the `charityType` string\nliteral, so the cursor type is narrowed for you: Tiltify hands back an\nopaque `metadata.after` string, Twitch a numeric `metadata.nextPage`.\n\n```ts\nimport { createDonationsFetcher } from \"@playlive/fundraiser-data\";\nimport type { TiltifyDonation } from \"@playlive/tiltify-core\";\n\nconst nextPage = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: \"6f4a1e2c-8b3d-4a11-9f77-2b0c5d9e1a44\",\n  count: 100,\n  config: { completedAfter: \"2026-02-01T00:00:00Z\" },\n});\n\nconst rows: TiltifyDonation[] = [];\nlet cursor: string | null | undefined = null;\ndo {\n  const page = await nextPage({ pageParam: cursor });\n  rows.push(...page.data);\n  cursor = page.metadata.after;\n} while (cursor);\n```\n\nFor a one-shot walk with a built-in page cap, prefer\n`fetchTiltifyFlattenedDonations({ campaignId, maxPages })`.\n\n### Pick this season's fundraising event\n\n```ts\nimport {\n  fetchTiltifyCurrentEvents,\n  selectCurrentFundraisingEvents,\n} from \"@playlive/fundraiser-data\";\n\n// Tiltify's cause endpoint returns years of history — filter to the\n// in-flight Play Live season, newest first.\nconst events = selectCurrentFundraisingEvents(await fetchTiltifyCurrentEvents());\nconst active = events[0]; // e.g. { name: \"PLAY LIVE 2026\", … }\n```\n\n### Drive a synthetic donation through the live pipeline\n\nUseful for overlay QA: the core API replays the payload down the same\nwebhook path a real donation takes, so alerts, donation trains, the\nsubathon timer, and every WebSocket subscriber react.\n\n```ts\nimport { postTiltifyTestDonations } from \"@playlive/fundraiser-data\";\n\nawait postTiltifyTestDonations({\n  donations: {\n    id: \"00000000-0000-0000-0000-0000000000ff\",\n    campaign_id: \"00000000-0000-0000-0000-000000000000\", // DEMO_CAMPAIGN_ID\n    cause_id: \"400f5687-6017-4d1a-a4d9-7c9166b984c2\", // DEFAULT_CAUSE_ID\n    amount: { value: \"125.00\", currency: \"USD\" },\n    donor_name: \"QA Bot\",\n    donor_comment: \"smoke test\",\n    completed_at: new Date().toISOString(),\n    donation_matches: null,\n    fundraising_event_id: null,\n    poll_id: null,\n    poll_option_id: null,\n    reward_claims: null,\n    reward_id: null,\n    sustained: null,\n    target_id: null,\n    team_event_id: null,\n  },\n  // Demo campaigns need no credential; real ones take one of:\n  // adminApiKey: process.env.PLAYLIVE_ADMIN_API_KEY,\n  // tiltifyOAuthToken: session.accessToken,\n});\n```\n\nMore end-to-end scenarios (donation rotation, demo-mode toggling,\nTanStack-Query plumbing) run against the in-process GreenRoom harness —\nsee `tests/integration/` in this package and\n[`docs/greenroom-cookbook.md`](../../docs/greenroom-cookbook.md).\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). For adding a new fetcher:\n\n1. Add the function to the appropriate `src/tiltify/` or\n   `src/twitch/` module with a TSDoc block.\n2. Re-export from `src/unified/index.ts` with `CharityType` dispatch\n   (Twitch returns `[]` / `null` for unsupported entities).\n3. Add a unit test that mocks `tiltify.*` (Tiltify side) or\n   `globalThis.fetch` (Twitch side); cover happy path + error path +\n   demo short-circuit.\n4. Append the symbol to the README's API reference table.\n5. Append a row to the CHANGELOG and the per-package section of the\n   root MIGRATION.md if it replaces an existing legacy symbol.\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/fundraiser-data/-/fundraiser-data-0.5.3.tgz","shasum":"0d51855b8c0b16e9abf0c5cc2490a0dac454a75a","integrity":"sha512-48DO3yMaRqAo1F85yOnsE3XkXrmbxphOTK766RrOnUHM07qyd5u1ILGaKRNfVeGUbPSyYmjXfxXUXTF5qF+DVg=="}},"0.5.4":{"name":"@playlive/fundraiser-data","version":"0.5.4","description":"Pure native-fetch REST surface for Tiltify + Twitch charity data — no React, no TanStack, no Zustand.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./config":{"import":"./config/index.js","types":"./config/index.d.ts"},"./demo":{"import":"./demo/index.js","types":"./demo/index.d.ts"},"./donation-trains":{"import":"./donation-trains/index.js","types":"./donation-trains/index.d.ts"},"./environments":{"import":"./environments/index.js","types":"./environments/index.d.ts"},"./tiltify":{"import":"./tiltify/index.js","types":"./tiltify/index.d.ts"},"./twitch":{"import":"./twitch/index.js","types":"./twitch/index.d.ts"},"./playlive":{"import":"./playlive/index.js","types":"./playlive/index.d.ts"},"./projections":{"import":"./projections/index.js","types":"./projections/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"},"./unified":{"import":"./unified/index.js","types":"./unified/index.d.ts"}},"peerDependencies":{"@playlive/tiltify-core":"^0.4.19","@playlive/twitch-charity":"^0.1.2","@playlive/realtime-pipeline":"^0.3.2"},"peerDependenciesMeta":{"@playlive/realtime-pipeline":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-tPaVMnAn4jhEwTh1nTKwO4SJZjFNNOQaa/JzpN2em+SSkXg2D3+m48JAwpAdN0JF4YTtnUkgEP2ErXmp3sqG2Q==","shasum":"2ad81383d6868d1cb8e01f41196b4be378957b0b","readme":"# @playlive/fundraiser-data\n\nPure native-fetch REST surface for Tiltify + Twitch charity data — **no\nReact, no TanStack Query, no Zustand**. Ported from\n`playlive-overlay-data-layer/src/api/*` with the React-aware glue\nstripped and the global `getConfig()` swapped for a self-contained\n`configure()` singleton.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/fundraiser-data\n```\n\nTwo of the three peers are **required** and are installed automatically\nby npm 7+ / Bun, so the one-liner above already pulls them in:\n\n| Peer                                                  | Required? | Why                                                                            |\n| ----------------------------------------------------- | --------- | ------------------------------------------------------------------------------ |\n| [`@playlive/tiltify-core`](../tiltify/core/)          | **yes**   | Every Tiltify fetcher drives the `tiltify` singleton and re-exports its types.  |\n| [`@playlive/twitch-charity`](../twitch/charity/)      | **yes**   | Owns the Twitch charity wire shapes, fetchers, and Tiltify converters.          |\n| [`@playlive/realtime-pipeline`](../realtime-pipeline/) | optional  | Demo fixtures only (`peerDependenciesMeta.optional`). See [Demo mode](#demo-mode). |\n\nTo pin them explicitly (recommended for apps that also import those\npackages directly, so a single version is hoisted):\n\n```bash\nbun add @playlive/fundraiser-data @playlive/tiltify-core @playlive/twitch-charity\nbun add @playlive/realtime-pipeline   # optional — demo fixtures only\n```\n\nPeers are declared jose-style so wire types stay in lockstep across\npackages. No runtime dependencies beyond the peers. Native `fetch` only.\n\n## Quick start\n\n```ts\nimport {\n  configure,\n  createDonationsFetcher,\n  fetchCampaign,\n  fetchMilestones,\n} from \"@playlive/fundraiser-data\";\n\n// Call this once at app boot.\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL, // deployed separately from the services below\n  twitchServiceUrl: \"https://main.playlive.core.api.experience.stjude.org\",\n  // Optional — only needed when the app touches these surfaces:\n  scheduleApiUrl: \"https://main.playlive.schedule.api.experience.stjude.org\",\n  lifetimeApiUrl: \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n  leaderboardApiUrl: \"https://main.playlive.leaderboard.api.experience.stjude.org\",\n});\n```\n\n> **Skip the URL boilerplate:** use\n> [`@playlive/fundraiser-data/environments`](#per-env-presets) to pull\n> the four Play Live service URLs (`twitchServiceUrl`, `lifetimeApiUrl`,\n> `leaderboardApiUrl`, `scheduleApiUrl`) from a versioned preset\n> instead of hand-wiring them.\n\n```ts\n// (imports from the block above)\n\n// Unified — works for both `tiltify` and `twitch`.\nconst campaign = await fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@some-user\",\n  slug: \"their-campaign\",\n});\n\nconst milestones = await fetchMilestones({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id,\n});\n\n// Cursor-aware donations fetcher (shape suits TanStack Query's\n// useInfiniteQuery, but works standalone).\nconst donations = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: campaign?.id ?? \"\",\n});\nconst page1 = await donations({ pageParam: null });\nconst page2 = await donations({ pageParam: page1.metadata.after });\n```\n\n`configure()` must run before any `fetch*()` call — `getConfig()` throws\n`\"@playlive/fundraiser-data not configured\"` otherwise, so a misordered\nboot sequence surfaces immediately instead of silently 404ing.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                  |\n| ------------------------------------ | ---------------------------------------------------------------------------- |\n| `@playlive/fundraiser-data`          | Default barrel — re-exports every subpath below, plus `PACKAGE_NAME` + `KNOWN_URLS`. |\n| `@playlive/fundraiser-data/config`   | `configure`, `getConfig`, `isConfigured`, `resetConfig`, `setDemoProvider`, `getDemoProvider`, `resetDemoProvider`, `DEFAULT_CAUSE_ID`, `DEFAULT_CONFIG` + the `FundraiserDataConfig` / `DemoProvider` types. |\n| `@playlive/fundraiser-data/tiltify`  | `fetchTiltifyCampaign`, `createTiltifyDonationsFetcher`, `fetchTiltifyFlattenedDonations`, `fetchTiltifyMilestones`, `fetchTiltifyRewards`, `fetchTiltifyPolls`, `fetchTiltifyTargets`, `fetchTiltifySchedule`, `fetchTiltifyUser`, `fetchTiltifyTeam`, `fetchTiltifyFundraisingEvent`, `fetchTiltifyFundraisingEventMilestones`, `fetchTiltifyCause`, `fetchTiltifyEventCampaigns`, `fetchTiltifyCurrentEvents`, `fetchTiltifyUserCampaigns`, `fetchTiltifyUserAndTeamCampaigns`, `createTiltifyLeaderboardFetcher` |\n| `@playlive/fundraiser-data/twitch`   | `fetchTwitchCampaign`, `fetchTwitchCampaignDonations`, `createTwitchDonationsFetcher`, `convertTwitchToTiltifyCampaign`, `convertTwitchToTiltifyDonation`, `TwitchApiError` |\n| `@playlive/fundraiser-data/playlive` | `fetchScheduleBlockRaised`, `fetchLifetimeRaised`, `fetchPreviousYearTotals`, `fetchGiftsThatGiveMilestones`, `bucketGiftsThatGiveGoal`, `GIFTS_THAT_GIVE_MILESTONE_GOALS`, `fetchDonorSpotlight`, `fetchLeaderboardExclusions`, `insertLeaderboardExclusion`, `deleteLeaderboardExclusion`, `fetchLeaderboardWithExclusions`, `postTiltifyTestDonations` |\n| `@playlive/fundraiser-data/donation-trains` | `fetchDonationTrains`, `fetchDonationTrainHighRateDonors`, `fetchDonationTrainCommonTrains`, `fetchUpdatedTrainStatus`, `updateTrainVisibility`, `processDonationsForTrains`, `fetchCampaignRulesets`, `createCampaignRuleset`, `updateRuleset`, `deleteRuleset`, `requireDonationTrainApiUrl` |\n| `@playlive/fundraiser-data/projections` | `extractCampaignAmounts`, `extractCampaignFundraisingEventAmounts`, `flattenDonationPages`, `getDonorLevel`, `DONOR_LEVEL_THRESHOLDS`, `selectCurrentFundraisingEvents` — pure React-free projections over the Tiltify domain types. |\n| `@playlive/fundraiser-data/environments` | Per-env `FundraiserDataConfig` presets — `DEV_CONFIG`, `QA_CONFIG`, `PROD_CONFIG`, `getConfigForEnv(env, overrides?)`, `buildPreset`, `ENV_URLS`, `GENERATED_AT`. |\n| `@playlive/fundraiser-data/unified`  | `CharityType`-dispatched `fetchCampaign` / `createDonationsFetcher` / `fetchMilestones` / `fetchRewards` / `fetchPolls` / `fetchTargets` / `fetchSchedule` / `fetchUser` / `fetchTeam` / `fetchFundraisingEvent` / `fetchFundraisingEventMilestones` / `fetchCause` / `fetchEventCampaigns`. |\n| `@playlive/fundraiser-data/demo`     | `isDemoMode`, `isDemoCampaignId`, `isDemoFundraisingEventId`, `stripSigil` + the `DEMO_*` slug / ID constants (no fixtures — see [Demo mode](#demo-mode)). |\n| `@playlive/fundraiser-data/types`    | `CharityType`, `DonationFetchConfig`, `PaginatedResponse`, `TiltifyPaginationMetadata`, `TwitchPaginationMetadata`. |\n\n### Per-env presets\n\n`@playlive/fundraiser-data/environments` ships versioned\n`FundraiserDataConfig` presets for the three Play Live service\nenvironments. The four URL fields (`twitchServiceUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `scheduleApiUrl`) are generated\nfrom the deployed API domains, so the presets track the live\nenvironments rather than a hand-typed copy.\n\n```ts\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    // The Tiltify proxy is deployed separately — supply your own.\n    tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  }),\n);\n```\n\nExports:\n\n| Export                                | Description                                                                                       |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| `DEV_CONFIG` / `QA_CONFIG` / `PROD_CONFIG` | Frozen `FundraiserEnvPreset` objects — four URLs + `causeId`. `tiltifyProxyUrl` deliberately absent. |\n| `getConfigForEnv(env, overrides?)`    | Merges a preset with overrides and returns a `FundraiserDataConfig` ready for `configure()`. Throws on an unknown `env`. |\n| `buildPreset(env)`                    | Pure preset builder behind the three frozen constants. Exported for tests.                        |\n| `ENV_URLS`                            | Raw URL table keyed by `FundraiserEnv` — useful for consumers that only want one field.           |\n| `GENERATED_AT`                        | ISO-8601 timestamp of the last URL-table refresh.                                                  |\n\nOverrides always win over the preset — handy for pointing a QA\nbuild at a locally-run schedule API. Any field of\n`FundraiserDataConfig` is fair game.\n\nEach published version pins the URL table it shipped with;\n`GENERATED_AT` tells you when that table was last refreshed.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/fundraiser-data/docs/>\n\n### Configuration (`./config`)\n\n| Export                | Kind      | Notes                                                                     |\n| --------------------- | --------- | ------------------------------------------------------------------------- |\n| `configure`           | function  | `configure(config: FundraiserDataConfig): void`. Idempotent — a later call replaces the previous config. |\n| `getConfig`           | function  | Returns `Required<FundraiserDataConfig>`. **Throws** when called before `configure()`. |\n| `isConfigured`        | function  | Non-throwing `boolean` probe.                                             |\n| `resetConfig`         | function  | Wipes config. Tests should call this in `afterEach`.                       |\n| `setDemoProvider`     | function  | Inject demo fixtures (typically the whole `@playlive/realtime-pipeline/demo` namespace). Pass `null` to detach. |\n| `getDemoProvider` / `resetDemoProvider` | function | Read back / clear the registered provider.               |\n| `FundraiserDataConfig` | interface | `tiltifyProxyUrl` (required) + `twitchServiceUrl`, `causeId`, `scheduleApiUrl`, `lifetimeApiUrl`, `leaderboardApiUrl`, `donorSpotlightApiUrl`, `donationTrainApiUrl`. |\n| `DemoProvider`        | interface | All-optional `getDemo*` methods; unimplemented ones fall back to `null` / `[]`. |\n| `DEFAULT_CAUSE_ID`    | const     | St. Jude cause UUID — the default for `config.causeId`.                    |\n| `DEFAULT_CONFIG`      | const     | Defaults merged under the consumer config (every optional URL defaults to `\"\"`). |\n\nState is anchored on a `Symbol.for()` slot on `globalThis`, so duplicate\nmodule copies produced by a bundler's `optimizeDeps` pre-bundling still\nresolve the same singleton.\n\n### Fetchers\n\n| Export                            | Source         | Notes                                                                |\n| --------------------------------- | -------------- | -------------------------------------------------------------------- |\n| `fetchCampaign`                   | `./unified`    | `CharityType`-dispatched campaign fetcher. Twitch payloads are projected into the Tiltify shape. |\n| `createDonationsFetcher`          | `./unified`    | Cursor-aware donations fetcher factory. Overloaded: a `\"tiltify\"` literal yields a string-cursor closure (`metadata.after`), `\"twitch\"` a numeric-page one (`metadata.nextPage`). |\n| `fetchMilestones` / `fetchRewards` / `fetchPolls` / `fetchTargets` / `fetchSchedule` | `./unified` | Twitch returns `[]` for all five (unsupported).             |\n| `fetchUser` / `fetchTeam` / `fetchFundraisingEvent` / `fetchCause` | `./unified` | Twitch returns `null` (unsupported).                     |\n| `fetchEventCampaigns` / `fetchFundraisingEventMilestones` | `./unified` | Twitch returns `[]` (unsupported).                   |\n| `fetchTiltify*`                   | `./tiltify`    | Per-entity Tiltify-only fetchers. `fetchTiltifyCampaign` **throws** `\"Campaign not found\"` when neither `id` nor `(teamUserSlug, slug)` resolves; the collection fetchers swallow errors → `[]`. |\n| `fetchTiltifyUserCampaigns`       | `./tiltify`    | Personal campaigns owned by a Tiltify user (by user UUID). Nullish / `\"null\"` string guard. |\n| `fetchTiltifyUserAndTeamCampaigns` | `./tiltify`   | Union of personal + team campaigns for a Tiltify user (by user UUID). Backs the landing \"pick a campaign\" flow. |\n| `fetchTiltifyFlattenedDonations`  | `./tiltify`    | Walks the cursor; capped at `maxPages` (default 100 pages × 100 rows). |\n| `fetchTiltifyCurrentEvents`       | `./tiltify`    | Cause-level fundraising-event list (`GET public/causes/{id}/fundraising_events`, limit 100). Every year, published or not. Swallows errors → `[]`. |\n| `createTiltifyLeaderboardFetcher` | `./tiltify`    | Cursor-aware Tiltify donor-leaderboard fetcher factory (shape-compatible with `useInfiniteQuery`). |\n| `fetchTwitch*` / `convertTwitchTo*` | `./twitch`   | Twitch-only fetchers + pure shape adapters (re-exported from `@playlive/twitch-charity`, with `causeId` injected from the singleton config). |\n| `TwitchApiError`                  | `./twitch`     | Thrown on non-2xx from the Twitch proxy. **Deprecated alias** of `TwitchCharityApiError` — same class, so `instanceof` matches either name. |\n| `fetchScheduleBlockRaised`        | `./playlive`   | Play Live schedule-block REST baseline (`GET /schedules/campaigns/{id}/raised`). Requires `scheduleApiUrl`. |\n| `fetchLifetimeRaised`             | `./playlive`   | Lifetime raised total for a user / team (`GET /getLifetimeRaised`). Returns `null` on `NODATA`. Requires `lifetimeApiUrl`. |\n| `fetchPreviousYearTotals`         | `./playlive`   | Historical yearly totals (`GET /getPreviousYearTotals`). Requires `lifetimeApiUrl`. |\n| `fetchGiftsThatGiveMilestones`    | `./playlive`   | Ordered gifts-that-give rows for a goal (`GiftsThatGiveMilestoneGoal` literal or raw number). Requires `lifetimeApiUrl`. |\n| `bucketGiftsThatGiveGoal`         | `./playlive`   | Snap an arbitrary goal amount **down** to the highest `GIFTS_THAT_GIVE_MILESTONE_GOALS` tier it clears (`1200 → \"1000\"`). Returns `null` below the `$100` floor. |\n| `fetchLeaderboardExclusions`      | `./playlive`   | Donor-name exclusion list read (`GET /leaderboard-exclusions/{id}`). Public. Requires `leaderboardApiUrl`. |\n| `insertLeaderboardExclusion` / `deleteLeaderboardExclusion` | `./playlive` | Exclusion mutations. Accept `adminApiKey` (`x-api-key`) **or** `tiltifyOAuthToken` (`Authorization: OAuth <token>`). |\n| `fetchLeaderboardWithExclusions`  | `./playlive`   | Server-filtered leaderboard (`GET /leaderboard-with-exclusions/{id}`). Supports fixed calendar buckets (`timeType`) or ad-hoc windows (`startDate` / `endDate`). Returns `MonetaryLeaderboardEntry[]`. |\n| `fetchDonorSpotlight`             | `./playlive`   | Donor spotlight overview (`GET /spotlight/overview`) for a campaign — donor-of-the-hour, biggest-donation-of-the-day, community hero. Returns `null` on non-2xx. Requires `donorSpotlightApiUrl`. |\n| `postTiltifyTestDonations`        | `./playlive`   | Fire a synthetic donation / batch through the core REST API (`POST /donations/tiltify/test`) so alerts, trains, timers and every WS subscriber react as if Tiltify delivered it. Accepts `adminApiKey` **or** `tiltifyOAuthToken`; demo campaigns need neither. Requires `twitchServiceUrl`. |\n| `fetchDonationTrains` / `fetchDonationTrainHighRateDonors` / `fetchDonationTrainCommonTrains` / `fetchUpdatedTrainStatus` | `./donation-trains` | Donation-train reads (`GET /get-trains-for-campaign/{id}`, `/get-stats/*`, `/get-updated-train-status/{id}`). Requires `donationTrainApiUrl`. |\n| `updateTrainVisibility` / `processDonationsForTrains` | `./donation-trains` | Train mutations (`PATCH /trains/{id}`, `POST /process-donations/`). Requires `donationTrainApiUrl`. |\n| `fetchCampaignRulesets` / `createCampaignRuleset` / `updateRuleset` / `deleteRuleset` | `./donation-trains` | Full CRUD on donation-train rulesets. Requires `donationTrainApiUrl`. |\n\n### Projections (`./projections`)\n\nPure, network-free functions over the Tiltify domain types — safe to\ncall from a Node warmer, a Lambda, or a React render.\n\n| Export                                  | Kind     | Notes                                                                  |\n| --------------------------------------- | -------- | ---------------------------------------------------------------------- |\n| `extractCampaignAmounts`                | function | `CampaignLike \\| null` → `{ totalAmount, currentAmount, goalAmount, originalGoalAmount, supportingAmount }`, all defensively parsed to numbers (`0` on missing / NaN). |\n| `extractCampaignFundraisingEventAmounts` | function | Same idea across a campaign + its parent fundraising event. `{ forceCampaignGoal: true }` pins `overallGoalAmount` to the campaign's own goal instead of the umbrella event goal. |\n| `flattenDonationPages`                  | function | `{ pages: [{ data }] }` → one sorted `TiltifyDonation[]`. Newest first; `{ flipSorting: true }` for oldest first. Non-donation rows are dropped. |\n| `getDonorLevel`                         | function | `number \\| string` → `DonorLevel`. Truncates before comparison, so `24.99 → \"grey\"`. |\n| `DONOR_LEVEL_THRESHOLDS`                | const    | Frozen ascending ladder: bronze 25 / silver 50 / gold 75 / platinum 100. |\n| `selectCurrentFundraisingEvents`        | function | Narrows a raw fundraising-event list to the in-flight Play Live season, newest first. Accepts `{ now }` for deterministic tests. |\n\n### Demo + package metadata\n\n| Export                            | Kind     | Notes                                                                |\n| --------------------------------- | -------- | -------------------------------------------------------------------- |\n| `isDemoMode(userOrTeamSlug, campaignSlug)` | function | Predicate over an already-sigil-stripped slug pair.        |\n| `isDemoCampaignId` / `isDemoFundraisingEventId` | function | UUID-sentinel predicates.                          |\n| `stripSigil`                      | function | Drops a leading `@` (user) or `+` (team) from a slug; type-preserving overloads. |\n| `DEMO_USER_SLUG`, `DEMO_TEAM_SLUG`, `DEMO_CAMPAIGN_SLUG`, `DEMO_TEAM_CAMPAIGN_SLUG`, `DEMO_CAMPAIGN_ID`, `DEMO_TEAM_CAMPAIGN_ID`, `DEMO_FUNDRAISING_EVENT_ID` | const | Identifier constants (zero fixtures inlined). |\n| `PACKAGE_NAME`                    | const    | Identifier for runtime version-pinning.                              |\n| `KNOWN_URLS`                      | const    | Twitch Extension URL disclosure list (frozen, empty).                |\n\n## Demo mode\n\nThe fetchers transparently short-circuit to demo fixtures when the\nincoming slugs or IDs match the demo identifiers — **no consumer-side\nbranching required**. Fixtures themselves live in\n`@playlive/realtime-pipeline/demo` (≈25 KB of canned data) and are\ninjected at app boot:\n\n```ts\nimport { fetchCampaign, setDemoProvider } from \"@playlive/fundraiser-data\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nsetDemoProvider(demo);\n\n// Now any fetch call with a demo slug returns the canned fixture\n// without touching the network.\nawait fetchCampaign({\n  charityType: \"tiltify\",\n  teamUserSlug: \"@playliveDemoUser\",\n  slug: \"playliveDemoCampaign\",\n});\n```\n\nThe `@` / `+` sigil is optional — `fetchTiltifyCampaign` runs\n`stripSigil()` before matching, so `\"@playliveDemoUser\"` and\n`\"playliveDemoUser\"` behave identically.\n\nIf no provider is registered, demo slugs resolve to `null` / `[]`\nrather than hitting Tiltify — safer than leaking real network traffic\nfrom a demo overlay misconfiguration.\n\n## Upstream spec\n\nTiltify data is read through the Tiltify v5 REST API — see Tiltify's\npublic developer documentation at <https://developers.tiltify.com> for\nthe upstream resource shapes. The typed client and domain types are\nre-exported transitively via\n[`@playlive/tiltify-core`](../tiltify/core/).\n\nThe Twitch Charity proxy is operated by Play Live and has no public\nspec; the canonical wire shapes live in\n[`@playlive/twitch-charity`](../twitch/charity/) and are version-pinned\nby tests.\n\nThe Play Live first-party services wrapped by the `/playlive` and\n`/donation-trains` subpaths (schedule, lifetime-raised, leaderboard,\ndonor spotlight, donation trains, and the core REST API) each publish\ntheir own Swagger UI at `<serviceUrl>/docs` — point it at whichever URL\nyou passed to `configure()`.\n\n## Twitch Extension URL disclosure\n\nThis package itself does not hard-code any production hosts — every\nendpoint flows through the consumer-supplied URLs passed to\n`configure()`. The `KNOWN_URLS` export is therefore empty:\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/fundraiser-data\";\nconsole.log(KNOWN_URLS);\n// []\n```\n\nYour overlay app must add every URL it passes to `configure()`\n(`tiltifyProxyUrl`, `twitchServiceUrl`, `scheduleApiUrl`,\n`lifetimeApiUrl`, `leaderboardApiUrl`, `donorSpotlightApiUrl`,\n`donationTrainApiUrl`) — or the corresponding `ENV_URLS` row when using\na preset — to its own Extension URL disclosure.\n\n## Migration from `playlive-overlay-data-layer`\n\n`@playlive/fundraiser-data` is a drop-in replacement for the\n`playlive-overlay-data-layer/src/api/*` layer. Function names,\nparameter shapes, and return shapes are preserved verbatim — only:\n\n- `getConfig()` from `playlive-overlay-data-layer/types/config` → call\n  `configure({ tiltifyProxyUrl, twitchServiceUrl, causeId })` once at\n  boot instead.\n- Demo short-circuit no longer hard-imports demo fixtures — register\n  them with `setDemoProvider(demo)` once. If you don't, demo slugs\n  return `null` / `[]` rather than the canned fixtures.\n- Every Twitch fetch accepts an optional `AbortSignal` for React\n  unmount cancellation.\n\n## Examples\n\n### Boot an overlay data layer end-to-end\n\nConfig → campaign → children in parallel → pure projections. This is\nthe shape every Play Live overlay uses; the React flavour of the same\nflow lives in [`@playlive/react-data`](../react-data/).\n\n```ts\nimport {\n  configure,\n  DEMO_CAMPAIGN_SLUG,\n  DEMO_USER_SLUG,\n  extractCampaignAmounts,\n  fetchMilestones,\n  fetchTiltifyCampaign,\n  fetchTiltifyFlattenedDonations,\n  getConfigForEnv,\n  getDonorLevel,\n  setDemoProvider,\n} from \"@playlive/fundraiser-data\";\nimport type { TiltifyDonation, TiltifyMilestone } from \"@playlive/tiltify-core\";\nimport * as demo from \"@playlive/realtime-pipeline/demo\";\n\nconfigure(\n  getConfigForEnv(\"prod\", {\n    tiltifyProxyUrl: \"https://tiltify-proxy.prod.experience.stjude.org\",\n  }),\n);\n\n// Optional: makes the demo slugs below resolve without any network.\nsetDemoProvider(demo);\n\nexport interface OverlaySnapshot {\n  campaignName: string;\n  raised: number;\n  goal: number;\n  percent: number;\n  nextMilestone: TiltifyMilestone | undefined;\n  topDonors: Array<{ name: string; amount: string; level: string }>;\n}\n\nexport async function loadOverlay(\n  teamUserSlug: string,\n  campaignSlug: string,\n): Promise<OverlaySnapshot | null> {\n  let campaign: Awaited<ReturnType<typeof fetchTiltifyCampaign>>;\n  try {\n    // Throws \"Campaign not found\" when the slug pair resolves nothing;\n    // resolves `null` when the campaign belongs to another cause.\n    campaign = await fetchTiltifyCampaign({ teamUserSlug, slug: campaignSlug });\n  } catch (error) {\n    console.error(\"campaign lookup failed\", error);\n    return null;\n  }\n  if (!campaign) return null;\n\n  // Milestones swallow their own errors → `[]`; donations are capped to\n  // one page so a long-running campaign doesn't walk 10k rows per tick.\n  const [milestones, donations] = await Promise.all([\n    fetchMilestones({ charityType: \"tiltify\", campaignId: campaign.id }),\n    fetchTiltifyFlattenedDonations({\n      campaignId: campaign.id,\n      count: 50,\n      maxPages: 1,\n    }),\n  ]);\n\n  const { currentAmount, goalAmount } = extractCampaignAmounts(campaign);\n\n  const nextMilestone = milestones\n    .filter((m) => m.active)\n    .sort(\n      (a, b) => Number.parseFloat(a.amount.value) - Number.parseFloat(b.amount.value),\n    )\n    .find((m) => Number.parseFloat(m.amount.value) > currentAmount);\n\n  const topDonors = [...donations]\n    .sort(\n      (a: TiltifyDonation, b: TiltifyDonation) =>\n        Number.parseFloat(b.amount.value) - Number.parseFloat(a.amount.value),\n    )\n    .slice(0, 5)\n    .map((d) => ({\n      name: d.donor_name,\n      amount: d.amount.value,\n      level: getDonorLevel(d.amount.value), // \"platinum\" | … | \"grey\"\n    }));\n\n  return {\n    campaignName: campaign.name,\n    raised: currentAmount,\n    goal: goalAmount,\n    percent: goalAmount > 0 ? (currentAmount / goalAmount) * 100 : 0,\n    nextMilestone,\n    topDonors,\n  };\n}\n\n// Demo slugs resolve entirely from the injected fixtures.\nawait loadOverlay(DEMO_USER_SLUG, DEMO_CAMPAIGN_SLUG);\n```\n\n### Walk the donation cursor manually\n\n`createDonationsFetcher` is overloaded on the `charityType` string\nliteral, so the cursor type is narrowed for you: Tiltify hands back an\nopaque `metadata.after` string, Twitch a numeric `metadata.nextPage`.\n\n```ts\nimport { createDonationsFetcher } from \"@playlive/fundraiser-data\";\nimport type { TiltifyDonation } from \"@playlive/tiltify-core\";\n\nconst nextPage = createDonationsFetcher({\n  charityType: \"tiltify\",\n  campaignId: \"6f4a1e2c-8b3d-4a11-9f77-2b0c5d9e1a44\",\n  count: 100,\n  config: { completedAfter: \"2026-02-01T00:00:00Z\" },\n});\n\nconst rows: TiltifyDonation[] = [];\nlet cursor: string | null | undefined = null;\ndo {\n  const page = await nextPage({ pageParam: cursor });\n  rows.push(...page.data);\n  cursor = page.metadata.after;\n} while (cursor);\n```\n\nFor a one-shot walk with a built-in page cap, prefer\n`fetchTiltifyFlattenedDonations({ campaignId, maxPages })`.\n\n### Pick this season's fundraising event\n\n```ts\nimport {\n  fetchTiltifyCurrentEvents,\n  selectCurrentFundraisingEvents,\n} from \"@playlive/fundraiser-data\";\n\n// Tiltify's cause endpoint returns years of history — filter to the\n// in-flight Play Live season, newest first.\nconst events = selectCurrentFundraisingEvents(await fetchTiltifyCurrentEvents());\nconst active = events[0]; // e.g. { name: \"PLAY LIVE 2026\", … }\n```\n\n### Drive a synthetic donation through the live pipeline\n\nUseful for overlay QA: the core API replays the payload down the same\nwebhook path a real donation takes, so alerts, donation trains, the\nsubathon timer, and every WebSocket subscriber react.\n\n```ts\nimport { postTiltifyTestDonations } from \"@playlive/fundraiser-data\";\n\nawait postTiltifyTestDonations({\n  donations: {\n    id: \"00000000-0000-0000-0000-0000000000ff\",\n    campaign_id: \"00000000-0000-0000-0000-000000000000\", // DEMO_CAMPAIGN_ID\n    cause_id: \"400f5687-6017-4d1a-a4d9-7c9166b984c2\", // DEFAULT_CAUSE_ID\n    amount: { value: \"125.00\", currency: \"USD\" },\n    donor_name: \"QA Bot\",\n    donor_comment: \"smoke test\",\n    completed_at: new Date().toISOString(),\n    donation_matches: null,\n    fundraising_event_id: null,\n    poll_id: null,\n    poll_option_id: null,\n    reward_claims: null,\n    reward_id: null,\n    sustained: null,\n    target_id: null,\n    team_event_id: null,\n  },\n  // Demo campaigns need no credential; real ones take one of:\n  // adminApiKey: process.env.PLAYLIVE_ADMIN_API_KEY,\n  // tiltifyOAuthToken: session.accessToken,\n});\n```\n\nFor the React flavour of these flows — the same fetchers behind hooks —\nsee [`@playlive/react-data`](../react-data/) (dependency-free) or\n[`@playlive/react-query`](../react-query/) (TanStack Query).\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/fundraiser-data/-/fundraiser-data-0.5.4.tgz","shasum":"2ad81383d6868d1cb8e01f41196b4be378957b0b","integrity":"sha512-tPaVMnAn4jhEwTh1nTKwO4SJZjFNNOQaa/JzpN2em+SSkXg2D3+m48JAwpAdN0JF4YTtnUkgEP2ErXmp3sqG2Q=="}}},"time":{"0.5.2":"2026-08-26T18:09:59.623Z","modified":"2026-08-26T20:08:43.765Z","0.1.0":"2026-08-26T18:14:52.228Z","0.1.1":"2026-08-26T18:14:52.840Z","0.1.2":"2026-08-26T18:14:53.384Z","0.1.3":"2026-08-26T18:14:53.987Z","0.1.4":"2026-08-26T18:14:54.571Z","0.1.5":"2026-08-26T18:14:55.334Z","0.2.0":"2026-08-26T18:14:56.008Z","0.2.1":"2026-08-26T18:14:56.683Z","0.2.2":"2026-08-26T18:14:57.418Z","0.2.3":"2026-08-26T18:14:58.176Z","0.2.4":"2026-08-26T18:14:58.686Z","0.3.0":"2026-08-26T18:14:59.234Z","0.3.1":"2026-08-26T18:14:59.925Z","0.3.2":"2026-08-26T18:15:00.579Z","0.3.3":"2026-08-26T18:15:01.249Z","0.3.4":"2026-08-26T18:15:01.932Z","0.4.0":"2026-08-26T18:15:02.681Z","0.4.1":"2026-08-26T18:15:03.410Z","0.4.2":"2026-08-26T18:15:04.125Z","0.4.3":"2026-08-26T18:15:04.841Z","0.5.0":"2026-08-26T18:15:05.552Z","0.5.1":"2026-08-26T18:15:06.331Z","0.5.3":"2026-08-26T19:44:49.902Z","0.5.4":"2026-08-26T20:08:43.765Z"}}