@playlive/tiltify-graphql
    Preparing search index...

    @playlive/tiltify-graphql

    @playlive/tiltify-graphql

    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

    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).

    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 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.

    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.

    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[]

    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:

    • The gateway enforces a server-side query-text whitelist. You cannot send a slimmed-down variant of a whitelisted operation, so the 14 exported GET_*_QUERY strings mirror byte-for-byte what tiltify.com itself sends. Use them verbatim (directly, or via the wrapper methods).
    • Response types describe the schema subset those operations select on. When Tiltify ships a schema change, the query strings and types move in lock-step and the package gets a new release — pin the version if you depend on exact field sets.

    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.

    @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";
    

    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;
    }
    }

    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}` : "—",
    };
    }

    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" },
    );

    MIT © St. Jude Children's Research Hospital