← all packages

@playlive/tiltify-graphql

v0.1.6

npm install @playlive/tiltify-graphql

Requires @playlive:registry=https://packages.playlive.experience.stjude.org in your .npmrc. packument · tarball · API docs


Curated GraphQL client for the public Tiltify GraphQL endpoint (https://api.tiltify.com/). Ported from @playlive/tiltify-tools; the queries, types, and wire format are preserved verbatim so consumers can migrate by changing the import specifier alone.

Coverage

Install

bun add @playlive/tiltify-graphql

No peer dependencies and no runtime dependencies — peerDependencies in package.json is empty and the client uses the platform's fetch (injectable via the constructor's options.fetch).

Quick start

import { TiltifyGraphQL } from "@playlive/tiltify-graphql";

const gql = new TiltifyGraphQL();

// Cause-level lookups
const cause = await gql.getCauseBySlug("stjude");
const leaders = await gql.getCauseLeaderboards("stjude");

// Walk every donation on a fact
const all = await gql.getAllFactDonations(cause!.causeFactId, 100);
console.log(`Total donations: ${all.length}`);

// Resolve a tiltify.com URL to its fact id
const fact = await gql.getFactByVanityAndSlug({
  vanity: "ryantrahan",
  slug: "50states",
});

Subpath exports

Subpath Description
@playlive/tiltify-graphql Default barrel — re-exports everything below.
@playlive/tiltify-graphql/client Just the TiltifyGraphQL class.
@playlive/tiltify-graphql/errors TiltifyGraphQLError + isTiltifyGraphQLError (no client, no queries).
@playlive/tiltify-graphql/queries Raw query strings (GET_USER_BY_SLUG_QUERY etc.) for use with other clients.
@playlive/tiltify-graphql/types Type-only barrel (TiltifyCauseDetail, TiltifyDonationNode, …).
@playlive/tiltify-graphql/constants DEFAULT_GRAPHQL_URL, DEFAULT_CLIENT_LIBRARY.

API reference

Full generated API documentation: https://packages.playlive.experience.stjude.org/p/@playlive/tiltify-graphql/docs/

Top-level exports:

Export Source Kind Notes
TiltifyGraphQL ./client class The curated GraphQL client.
TiltifyGraphQLError ./errors class Thrown by every method. Carries operationName, errors[], status, isNotFound.
isTiltifyGraphQLError ./errors function Realm-safe type guard — prefer over instanceof across bundles.
TiltifyGraphQLErrorEntry, TiltifyGraphQLErrorOptions ./errors type Raw errors[] entry shape + constructor options.
GET_*_QUERY (14 constants) ./queries const One per operation; safe to use with any GraphQL client.
TiltifyCauseDetail + 34 other types ./types type The Tiltify GraphQL schema subset this package selects on.
TiltifyGraphQLOptions ./types type Constructor options (headers, fetch, clientLibrary).
DEFAULT_GRAPHQL_URL ./constants const "https://api.tiltify.com/".
DEFAULT_CLIENT_LIBRARY ./constants const Apollo extensions.clientLibrary default (@apollo/client 4.1.6).
KNOWN_URLS ./ const Twitch Extension URL disclosure list.
PACKAGE_NAME ./ const Identifier for runtime version-pinning.

TiltifyGraphQL methods

Every method returns the unwrapped data for its operation and throws TiltifyGraphQLError on failure. Methods documented as returning | null normalize Tiltify's not-found signal (see Error handling).

Method Returns
query<TData, TVars>(operationName, query, variables?) TData — low-level escape hatch for any operation.
getUserBySlug<TUser>(slug) TUser | null (defaults to TiltifyUser).
getCauseBySlug(slug) TiltifyCauseDetail | null
getCauseAndFundraisingEventBySlug({ causeSlug, feSlug }) { cause, fundraisingEvent } — each independently nullable.
getCauseLeaderboards(slug) { id, userLeaderboard, teamLeaderboard } | null
getFundraisingEventLeaderboards(id) All six FE leaderboards, or null.
getFactLeaderboards({ id, limit? }) { id, donorLeaderboard, userLeaderboard, teamLeaderboard } | null
getFactFitnessLeaderboards({ id, limit? }) Four fitness leaderboards, or null.
getFactByVanityAndSlug({ vanity, slug? }) TiltifyFactVanitySlug | null — resolves a URL to a fact id.
getFactDonations({ id, limit, cursor? }) TiltifyDonationConnection | null — one page, raw cursors.
getAllFactDonations(id, pageSize?) TiltifyDonationNode[] — walks every page.
getFactTopDonation(id) TiltifyDonationNode | null
getFactMilestones(id) TiltifyMilestone[] | null
getCurrentMissions() TiltifyMission[]
getLatestBadges() TiltifyLatestBadge[]
getUserBadges(userId) TiltifyBadgeGroup[]

Upstream spec

This package targets Tiltify's public GraphQL endpoint at https://api.tiltify.com/ (DEFAULT_GRAPHQL_URL). Tiltify publishes no schema or introspection for it — the documented public surface is the v5 REST API at https://developers.tiltify.com, wrapped separately by @playlive/tiltify-core.

Two consequences shape this client:

Twitch Extension URL disclosure

The KNOWN_URLS export enumerates every absolute URL or host this package can fetch — the list a Twitch Extension submission must disclose verbatim.

import { KNOWN_URLS } from "@playlive/tiltify-graphql";
console.log(KNOWN_URLS);
// ["https://api.tiltify.com"]

Keep this list and the source export in sync — the Extension submission form requires the disclosure list verbatim.

Migration from @playlive/tiltify-tools

@playlive/tiltify-graphql is a drop-in replacement for the GraphQL surface that used to live inside tiltify-tools. The TiltifyGraphQL class, every method signature, every exported type, and every query string constant are preserved unchanged. Only the import path moves:

- import { TiltifyGraphQL } from "@playlive/tiltify-tools/tiltify-graphql";
+ import { TiltifyGraphQL } from "@playlive/tiltify-graphql";

Examples

Error handling

Tiltify signals "no such resource" as HTTP 200 with {"errors":[{"message":"404"}]}, so the response status alone cannot tell a missing fact from an outage. TiltifyGraphQLError.isNotFound pre-computes that classification; isTiltifyGraphQLError is the realm-safe guard to reach for when bundler pre-bundling can produce two copies of the module.

import { isTiltifyGraphQLError, TiltifyGraphQL } from "@playlive/tiltify-graphql";

const gql = new TiltifyGraphQL();

try {
  const lb = await gql.getFundraisingEventLeaderboards("fe-does-not-exist");
  console.log(lb);
} catch (err) {
  if (isTiltifyGraphQLError(err)) {
    if (err.isNotFound) {
      console.info("no such fundraising event");
    } else {
      // Log-safe projection: drops `cause` and raw `extensions` payloads.
      console.error("tiltify graphql failed", err.toJSON());
      // → { name, message, operationName: "get_fe_leaderboards", status, isNotFound, errors: [...] }
    }
  } else {
    throw err;
  }
}

Resolve a landing-page URL, then read its milestones and top donors

The full path most overlays need: URL → fact id → fact-scoped data. Note that getFactByVanityAndSlug takes the vanity without its @ / + sigil, and that the fact id it returns is what every getFact* helper expects — not the FE's publicId.

import {
  TiltifyGraphQL,
  type TiltifyLeaderboardEntry,
  type TiltifyMilestone,
} from "@playlive/tiltify-graphql";

const gql = new TiltifyGraphQL();

export async function loadFundraisingEventPanel(vanity: string, slug: string): Promise<{
  factId: string;
  nextMilestone: TiltifyMilestone | null;
  topDonors: TiltifyLeaderboardEntry[];
  topDonationLabel: string;
} | null> {
  // 1. tiltify.com/@stjude/relay-for-st-jude-2026 → fact id
  const fact = await gql.getFactByVanityAndSlug({ vanity, slug });
  if (!fact) return null;

  // 2. Fan out across fact-scoped operations.
  const [milestones, leaderboards, top] = await Promise.all([
    gql.getFactMilestones(fact.id),
    gql.getFactLeaderboards({ id: fact.id, limit: 10 }),
    gql.getFactTopDonation(fact.id),
  ]);

  // `active` flags the current "next unhit" milestone upstream.
  const nextMilestone = milestones?.find((m) => m.active) ?? null;

  // Leaderboards are Relay connections — unwrap edges → node.
  const topDonors =
    leaderboards?.donorLeaderboard?.entries.edges.map((edge) => edge.node) ?? [];

  return {
    factId: fact.id,
    nextMilestone,
    topDonors,
    topDonationLabel: top ? `${top.donorName ?? "Anonymous"} — ${top.amount.value}` : "—",
  };
}

Manual pagination and a custom fetch

getAllFactDonations walks every page for you; drive getFactDonations directly when you want to stream or stop early. The constructor takes an endpoint override plus headers / fetch / clientLibrary — inject fetch to add caching, tracing, or a test double.

import { TiltifyGraphQL, type TiltifyDonationNode } from "@playlive/tiltify-graphql";

const gql = new TiltifyGraphQL("https://api.tiltify.com/", {
  headers: { "X-Source": "playlive-console" },
  fetch: (input, init) => {
    console.debug("→ tiltify graphql", input);
    return globalThis.fetch(input, init);
  },
});

export async function* streamDonations(
  factId: string,
  pageSize = 100,
): AsyncGenerator<TiltifyDonationNode> {
  let cursor: string | null = null;

  while (true) {
    const page = await gql.getFactDonations({ id: factId, limit: pageSize, cursor });
    if (!page) return;

    for (const edge of page.edges) {
      yield edge.node;
    }

    if (!page.pageInfo.hasNextPage || !page.pageInfo.endCursor) return;
    cursor = page.pageInfo.endCursor;
  }
}

Need an operation this client doesn't wrap? Pass one of the exported query strings straight to query — it applies the same envelope, headers, and error classification:

import { GET_DEFAULT_TEMPLATE_FACT_QUERY } from "@playlive/tiltify-graphql/queries";

// getFactMilestones() projects this payload down to `milestones`; go direct
// when you also need polls, rewards, sponsors, or the template config.
const data = await gql.query<{ fact: Record<string, unknown> | null }, { id: string }>(
  "get_default_template_fact",
  GET_DEFAULT_TEMPLATE_FACT_QUERY,
  { id: "fact-id" },
);

License

MIT © St. Jude Children's Research Hospital