npm install @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.
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. |
TiltifyGraphQL methodsEvery 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:
GET_*_QUERY strings mirror byte-for-byte what tiltify.com itself sends.
Use them verbatim (directly, or via the wrapper methods).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-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";
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}` : "—",
};
}
fetchgetAllFactDonations 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