TypeScript client for the GitHub REST API and GraphQL API. Works in Node.js and the browser (isomorphic). Fully typed, zero runtime dependencies.
npm install gh-api-clientimport { GitHubClient } from 'gh-api-client';
const gh = new GitHubClient({
token: 'ghp_yourPersonalAccessToken',
});For GitHub Enterprise Server, pass a custom apiUrl:
const gh = new GitHubClient({
token: 'ghp_yourPersonalAccessToken',
apiUrl: 'https://github.mycompany.com/api/v3',
});const me = await gh.currentUser();
// { login: 'octocat', name: 'The Octocat', ... }// Get a user's profile
const user = await gh.user('octocat');
// List a user's public repositories
const repos = await gh.user('octocat').repos();
const repos = await gh.user('octocat').repos({ sort: 'updated', per_page: 50 });
// Navigate into a user repository
const repo = await gh.user('octocat').repo('Hello-World');
const content = await gh.user('octocat').repo('Hello-World').raw('README.md');
// Followers and following
const followers = await gh.user('octocat').followers();
const following = await gh.user('octocat').following();
// List public events performed by a user
const events = await gh.user('octocat').publicEvents();
const events = await gh.user('octocat').publicEvents({ per_page: 30 });
// List social accounts configured on a user's profile (LinkedIn, npm, Twitter, etc.)
const accounts = await gh.user('octocat').socialAccounts();
// [{ provider: 'linkedin', url: 'https://linkedin.com/in/octocat' }, { provider: 'npm', url: 'https://npmjs.com/~octocat' }]
// List public organizations a user belongs to
const orgs = await gh.user('octocat').organizations();
const orgs = await gh.user('octocat').organizations({ per_page: 50 });// Get an organization
const org = await gh.org('github');
// List an organization's repositories
const repos = await gh.org('github').repos();
const repos = await gh.org('github').repos({ type: 'public', per_page: 100 });
// List members
const members = await gh.org('github').members();
const members = await gh.org('github').members({ role: 'admin' });
// Navigate into an org repository
const repo = await gh.org('github').repo('linguist');
const prs = await gh.org('github').repo('linguist').pullRequests({ state: 'open' });// Shortcut: access any repo by owner + name
const repo = await gh.repo('octocat', 'Hello-World');
// Repository info
const repo = await gh.repo('octocat', 'Hello-World');
// { id, name, full_name, owner, private, default_branch, ... }
// Forks
const forks = await gh.repo('octocat', 'Hello-World').forks();
const forks = await gh.repo('octocat', 'Hello-World').forks({ sort: 'newest' });
// Webhooks
const hooks = await gh.repo('octocat', 'Hello-World').webhooks();
// Topics
const topics = await gh.repo('octocat', 'Hello-World').topics();
// ['typescript', 'api-client']
// Contributors
const contributors = await gh.repo('octocat', 'Hello-World').contributors();
// Raw file content (returns string)
const content = await gh.repo('octocat', 'Hello-World').raw('README.md');
const content = await gh.repo('octocat', 'Hello-World').raw('src/index.ts', { ref: 'main' });
// Multiple raw files (returns Record<filePath, string>)
const files = await gh.repo('octocat', 'Hello-World').multipleRaw([
'README.md',
'src/index.ts',
], { ref: 'main' });
// { 'README.md': '# Hello World', 'src/index.ts': '...' }
// File/directory contents (returns GitHubContent or GitHubContent[])
const file = await gh.repo('octocat', 'Hello-World').contents('README.md');
const dir = await gh.repo('octocat', 'Hello-World').contents('src');
const root = await gh.repo('octocat', 'Hello-World').contents();// List branches
const branches = await gh.repo('octocat', 'Hello-World').branches();
const branches = await gh.repo('octocat', 'Hello-World').branches({ protected: true });
// Get a single branch
const branch = await gh.repo('octocat', 'Hello-World').branch('main');const tags = await gh.repo('octocat', 'Hello-World').tags();
const tags = await gh.repo('octocat', 'Hello-World').tags({ per_page: 50 });// List releases
const releases = await gh.repo('octocat', 'Hello-World').releases();
// Get the latest published release
const latest = await gh.repo('octocat', 'Hello-World').latestRelease();
// Get a release by ID
const release = await gh.repo('octocat', 'Hello-World').release(1);
// Create a release
const newRelease = await gh.repo('octocat', 'Hello-World').createRelease({
tag_name: 'v1.2.0',
name: 'v1.2.0',
body: '## Changelog\n- Fix bug #42',
});
// Publish a draft release
await gh.repo('octocat', 'Hello-World').updateRelease(1, { draft: false });
// Delete a release
await gh.repo('octocat', 'Hello-World').deleteRelease(1);const langs = await gh.repo('octocat', 'Hello-World').languages();
// { JavaScript: 1234567, TypeScript: 89012, CSS: 34567 }// List commits
const commits = await gh.repo('octocat', 'Hello-World').commits();
const commits = await gh.repo('octocat', 'Hello-World').commits({
sha: 'main',
path: 'src/index.ts',
author: 'octocat',
since: '2024-01-01T00:00:00Z',
until: '2024-12-31T23:59:59Z',
});
// Get a single commit (includes stats and files)
const commit = await gh.repo('octocat', 'Hello-World').commit('abc123def456');
// Commit statuses (from external CI/CD via Statuses API)
const statuses = await gh.repo('octocat', 'Hello-World').commit('abc123').statuses();
// Combined status (aggregate of all statuses)
const combined = await gh.repo('octocat', 'Hello-World').commit('abc123').combinedStatus();
// { state: 'success', total_count: 3, statuses: [...] }
// GitHub Actions check runs
const checks = await gh.repo('octocat', 'Hello-World').commit('abc123').checkRuns();
const checks = await gh.repo('octocat', 'Hello-World').commit('abc123').checkRuns({ status: 'completed' });
// Create a commit status (e.g., from a CI/CD system)
const status = await gh.repo('octocat', 'Hello-World').commit('abc123').createStatus({
state: 'success',
context: 'ci/circleci',
description: 'Build passed',
target_url: 'https://ci.example.com/build/42',
});
// List comments on a commit
const comments = await gh.repo('octocat', 'Hello-World').commit('abc123').comments();
// Add a comment to a commit
const comment = await gh.repo('octocat', 'Hello-World').commit('abc123').addComment({
body: 'Looks good to me!',
});// Get the tree of a branch (shallow — top-level entries only)
const tree = await gh.repo('octocat', 'Hello-World').gitTree('main');
// Get the full recursive tree (all files and folders)
const fullTree = await gh.repo('octocat', 'Hello-World').gitTree('main', { recursive: '1' });
console.log(fullTree.tree.map(item => item.path));
// ['src/index.ts', 'src/utils/helper.ts', ...]
// Also accepts a tree SHA or tag name
const tree = await gh.repo('octocat', 'Hello-World').gitTree('v1.0.0');
const tree = await gh.repo('octocat', 'Hello-World').gitTree('9fb037999f264ba9a7fc6274d15fa3ae2ab98312');// List pull requests
const prs = await gh.repo('octocat', 'Hello-World').pullRequests();
const prs = await gh.repo('octocat', 'Hello-World').pullRequests({
state: 'open',
base: 'main',
sort: 'updated',
direction: 'desc',
per_page: 50,
});
// Get a single pull request
const pr = await gh.repo('octocat', 'Hello-World').pullRequest(42);
// Commits in the PR
const commits = await gh.repo('octocat', 'Hello-World').pullRequest(42).commits();
// Changed files
const files = await gh.repo('octocat', 'Hello-World').pullRequest(42).files();
// Reviews
const reviews = await gh.repo('octocat', 'Hello-World').pullRequest(42).reviews();
// Inline review comments (diff comments)
const comments = await gh.repo('octocat', 'Hello-World').pullRequest(42).reviewComments();
// Check if merged
const merged = await gh.repo('octocat', 'Hello-World').pullRequest(42).isMerged();
// true | false
// Merge a pull request
const result = await gh.repo('octocat', 'Hello-World').pullRequest(42).merge();
const result = await gh.repo('octocat', 'Hello-World').pullRequest(42).merge({
merge_method: 'squash',
commit_title: 'feat: my feature (#42)',
});
// Submit a review
await gh.repo('octocat', 'Hello-World').pullRequest(42).createReview({ event: 'APPROVE' });
await gh.repo('octocat', 'Hello-World').pullRequest(42).createReview({
event: 'REQUEST_CHANGES',
body: 'Please fix the tests.',
});
// Request reviewers
await gh.repo('octocat', 'Hello-World').pullRequest(42).requestReviewers({
reviewers: ['octocat'],
team_reviewers: ['justice-league'],
});
// Add an inline diff comment
await gh.repo('octocat', 'Hello-World').pullRequest(42).addComment({
body: 'Should this be a constant?',
commit_id: 'abc123def456',
path: 'src/index.ts',
line: 10,
});
// Update pull request metadata
const updated = await gh.repo('octocat', 'Hello-World').pullRequest(42).update({
title: 'feat: improved feature',
state: 'closed',
});// List issues in a repository
const issues = await gh.repo('octocat', 'Hello-World').issues();
const issues = await gh.repo('octocat', 'Hello-World').issues({ state: 'open', labels: 'bug' });
// Get a single issue
const issue = await gh.repo('octocat', 'Hello-World').issue(1);
// Create an issue
const newIssue = await gh.repo('octocat', 'Hello-World').createIssue({
title: 'Something is broken',
body: 'Steps to reproduce...',
labels: ['bug'],
});
// Add a comment
const comment = await gh.repo('octocat', 'Hello-World').issue(1).addComment('Thanks for the report!');
// Update an issue (close it)
await gh.repo('octocat', 'Hello-World').issue(1).update({ state: 'closed', state_reason: 'completed' });
// Rename and reassign
await gh.repo('octocat', 'Hello-World').issue(1).update({ title: 'New title', assignees: ['octocat'] });// List labels
const labels = await gh.repo('octocat', 'Hello-World').labels();
// Get a single label
const label = await gh.repo('octocat', 'Hello-World').label('bug');
// Create a label
await gh.repo('octocat', 'Hello-World').createLabel({ name: 'enhancement', color: '84b6eb' });
// Update a label
await gh.repo('octocat', 'Hello-World').updateLabel('bug', { color: 'ee0701', description: 'Something is broken' });
// Delete a label
await gh.repo('octocat', 'Hello-World').deleteLabel('wontfix');// List milestones
const milestones = await gh.repo('octocat', 'Hello-World').milestones({ state: 'open' });
// Get a single milestone
const ms = await gh.repo('octocat', 'Hello-World').milestone(1);
// Create a milestone
await gh.repo('octocat', 'Hello-World').createMilestone({ title: 'v2.0', due_on: '2025-12-31T00:00:00Z' });
// Close a milestone
await gh.repo('octocat', 'Hello-World').updateMilestone(1, { state: 'closed' });
// Delete a milestone
await gh.repo('octocat', 'Hello-World').deleteMilestone(1);// List collaborators
const collabs = await gh.repo('octocat', 'Hello-World').collaborators();
const outside = await gh.repo('octocat', 'Hello-World').collaborators({ affiliation: 'outside' });
// Add a collaborator (sends an invitation if not already a collaborator)
await gh.repo('octocat', 'Hello-World').addCollaborator('hubot');
await gh.repo('octocat', 'Hello-World').addCollaborator('hubot', { permission: 'maintain' });
// Remove a collaborator
await gh.repo('octocat', 'Hello-World').removeCollaborator('hubot');// List issues across all repositories the authenticated user has access to
const { values } = await gh.issues({ filter: 'all', state: 'open', per_page: 100 });
// GitHub returns PRs mixed with issues — filter them out by checking pull_request
const realIssues = values.filter(i => !i.pull_request);
// filter values: 'assigned' | 'created' | 'mentioned' | 'subscribed' | 'repos' | 'all'// Search issues and PRs using GitHub's search syntax
const results = await gh.searchIssues({ q: 'is:issue is:open label:bug' });
console.log(`Found ${results.totalCount} issues`);
results.values; // GitHubIssue[]
// Search for open PRs authored by a user
const prs = await gh.searchIssues({ q: 'is:pr is:open author:octocat', sort: 'updated' });
// Search stale issues not updated in 30+ days
const stale = await gh.searchIssues({
q: 'is:issue is:open updated:<2024-01-01',
sort: 'updated',
order: 'asc',
});// List gists for the authenticated user
const gists = await gh.listGists();
const gists = await gh.listGists({ per_page: 50, since: '2024-01-01T00:00:00Z' });
// Get a single gist (awaitable directly)
const gist = await gh.gist('abc123');
// Create a gist
const newGist = await gh.createGist({
description: 'My snippet',
public: true,
files: {
'hello.ts': { content: 'console.log("hello")' },
},
});
// Update a gist
const updated = await gh.gist('abc123').update({
description: 'Updated description',
files: {
'hello.ts': { content: 'console.log("updated")' },
'old.ts': null, // deletes the file
},
});
// Delete a gist
await gh.gist('abc123').delete();
// Commits and forks
const commits = await gh.gist('abc123').commits();
const fork = await gh.gist('abc123').fork();
const forks = await gh.gist('abc123').forks();
// Star / unstar
await gh.gist('abc123').star();
await gh.gist('abc123').unstar();
const starred = await gh.gist('abc123').isStarred(); // true | false
// Comments
const comments = await gh.gist('abc123').comments();
const comment = await gh.gist('abc123').addComment({ body: 'Great snippet!' });
const updated = await gh.gist('abc123').updateComment(comment.id, { body: 'Even better!' });
await gh.gist('abc123').deleteComment(comment.id);// Search repositories using GitHub's search syntax
const results = await gh.searchRepos({ q: 'language:typescript stars:>1000' });
const results = await gh.searchRepos({ q: 'user:octocat', sort: 'stars', order: 'desc' });
console.log(`Found ${results.totalCount} repositories`);
results.values; // GitHubRepository[]const results = await gh.searchUsers({ q: 'location:Berlin language:typescript', sort: 'followers' });
console.log(`Found ${results.totalCount} users`);
results.values; // GitHubUser[]const results = await gh.searchCode({ q: 'addClass repo:jquery/jquery' });
const results = await gh.searchCode({ q: 'useState language:typescript', sort: 'indexed' });
console.log(`Found ${results.totalCount} files`);
results.values; // GitHubCodeResult[] — each has name, path, sha, html_url, repository// List unread notification threads for the authenticated user
const { values } = await gh.notifications();
// Include already-read notifications
const { values } = await gh.notifications({ all: true });
// Only notifications the user participates in or is mentioned
const { values } = await gh.notifications({ participating: true, per_page: 50 });
// Each notification has: id, reason, unread, subject (title, url, type), repository, updated_at
const notification = values[0];
console.log(notification.reason); // 'mention' | 'review_requested' | 'assign' | ...
console.log(notification.subject.type); // 'Issue' | 'PullRequest' | 'Release' | 'CheckSuite'
// Mark a single thread as read (returns void, GitHub responds 205 No Content)
await gh.markNotificationRead('123456789');
// Mark all notifications as read
await gh.markAllNotificationsRead();// List workflow runs for a repository (latest first by default)
const { total_count, workflow_runs } = await gh.repo('octocat', 'Hello-World').workflowRuns();
// Filter by branch, status, or event
const { workflow_runs } = await gh.repo('octocat', 'Hello-World').workflowRuns({
branch: 'main',
status: 'completed',
per_page: 10,
});
const lastRun = workflow_runs[0];
console.log(lastRun.conclusion); // 'success' | 'failure' | 'cancelled' | null ...
console.log(lastRun.status); // 'completed' | 'in_progress' | 'queued' | ...
// Compute success rate across recent runs
const rate = workflow_runs.filter(r => r.conclusion === 'success').length / workflow_runs.length;
// List workflow definitions
const { workflows } = await gh.repo('octocat', 'Hello-World').workflows();
// Get a single run
const run = await gh.repo('octocat', 'Hello-World').workflowRun(12345);
// Cancel a run in progress
await gh.repo('octocat', 'Hello-World').cancelWorkflowRun(12345);
// Trigger a workflow dispatch
await gh.repo('octocat', 'Hello-World').triggerWorkflow('ci.yml', { ref: 'main' });
await gh.repo('octocat', 'Hello-World').triggerWorkflow('ci.yml', { ref: 'main', inputs: { environment: 'staging' } });// List global advisories from the GitHub Advisory Database
const advisories = await gh.advisories();
const advisories = await gh.advisories({
severity: 'critical',
ecosystem: 'npm',
sort: 'published',
direction: 'desc',
});
// Get a single global advisory by GHSA ID
const advisory = await gh.advisory('GHSA-1234-5678-9abc');
// Get a global advisory by CVE ID (returns null if not found)
const advisory = await gh.advisoryByCve('CVE-2021-44228');
if (advisory) {
console.log(advisory.summary);
console.log(advisory.severity); // 'critical' | 'high' | 'medium' | 'low' | 'unknown'
}
// List security advisories for a repository
const repoAdvisories = await gh.repo('octocat', 'Hello-World').repoAdvisories();
const repoAdvisories = await gh.repo('octocat', 'Hello-World').repoAdvisories({ state: 'published' });
// Get a single repository advisory by GHSA ID
const repoAdvisory = await gh.repo('octocat', 'Hello-World').repoAdvisory('GHSA-1234-5678-9abc');
// Create a repository advisory draft
const draft = await gh.repo('octocat', 'Hello-World').createAdvisory({
summary: 'Remote code execution via crafted input',
description: 'A vulnerability in...',
severity: 'critical',
});
// Update a repository advisory
const updated = await gh.repo('octocat', 'Hello-World').updateAdvisory('GHSA-1234-5678-9abc', {
state: 'published',
});
// Request a CVE ID for a repository advisory
const result = await gh.repo('octocat', 'Hello-World').requestCve('GHSA-1234-5678-9abc');The client exposes a contributionMap() method on UserResource that queries the GitHub GraphQL API to fetch the same annual contribution calendar shown on a user's profile. It also provides a generic graphql<T>() escape hatch for any other GraphQL query.
Note: The GraphQL API always requires authentication. Any valid token works to query another user's public contributions. To include private contributions you must use the queried user's own token.
// Last year (GitHub default)
const calendar = await gh.user('octocat').contributionMap();
console.log(calendar.totalContributions); // 847
// Flatten to a list of days
const days = calendar.weeks.flatMap(w => w.contributionDays);
// [{ date: '2024-01-01', contributionCount: 3, color: '#216e39' }, ...]
// Specific date range
const q1 = await gh.user('octocat').contributionMap({
from: '2024-01-01T00:00:00Z',
to: '2024-03-31T23:59:59Z',
});Each ContributionDay contains:
| Field | Type | Description |
|---|---|---|
date |
string |
ISO date, e.g. '2024-01-15' |
contributionCount |
number |
Number of contributions on that day |
color |
string |
Hex intensity color, e.g. '#216e39' |
// Repos the user committed to (current year)
const commits = await gh.user('octocat').commitContributionsByRepo();
// [{ repository: { nameWithOwner: 'octocat/Hello-World', url: '...' }, totalCount: 42 }, ...]
// Repos where the user opened pull requests
const prs = await gh.user('octocat').pullRequestContributionsByRepo();
// Repos where the user opened issues
const issues = await gh.user('octocat').issueContributionsByRepo();const pinned = await gh.user('octocat').pinnedItems();
// Up to 6 items — each is a PinnedRepository or PinnedGist
for (const item of pinned) {
if ('nameWithOwner' in item) {
console.log(item.nameWithOwner, item.stargazerCount); // repository
} else {
console.log(item.name); // gist
}
}const result = await gh.graphql<{ viewer: { login: string } }>(`
query { viewer { login } }
`);
console.log(result.viewer.login);
// With variables
const result = await gh.graphql<{ user: { bio: string } }>(
`query($login: String!) { user(login: $login) { bio } }`,
{ login: 'octocat' },
);Every list method returns a GitHubPagedResponse<T>:
const page = await gh.repo('octocat', 'Hello-World').commits({ per_page: 30 });
page.values // GitHubCommit[] — the items on this page
page.hasNextPage // boolean
page.nextPage // number | undefined — pass as `page` to get the next page
page.totalCount // number | undefined — only available on search endpointsIterate all pages:
let page = 1;
let hasMore = true;
while (hasMore) {
const res = await gh.repo('octocat', 'Hello-World').commits({ per_page: 100, page });
process(res.values);
hasMore = res.hasNextPage;
page = res.nextPage ?? page + 1;
}Subscribe to every HTTP request made by the client for logging, monitoring, or debugging:
gh.on('request', (event) => {
console.log(`[${event.method}] ${event.url} → ${event.statusCode} (${event.durationMs}ms)`);
if (event.error) {
console.error('Request failed:', event.error.message);
}
});The event object contains:
| Field | Type | Description |
|---|---|---|
url |
string |
Full URL that was requested |
method |
'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT' |
HTTP method used |
startedAt |
Date |
When the request started |
finishedAt |
Date |
When the request finished |
durationMs |
number |
Duration in milliseconds |
statusCode |
number | undefined |
HTTP status code, if a response was received |
error |
Error | undefined |
Present only if the request failed |
Multiple listeners can be registered. on() returns this for chaining.
Non-2xx responses throw a GitHubApiError with the HTTP status code and status text:
import { GitHubApiError } from 'gh-api-client';
try {
await gh.repo('octocat', 'nonexistent-repo');
} catch (err) {
if (err instanceof GitHubApiError) {
console.log(err.status); // 404
console.log(err.statusText); // 'Not Found'
console.log(err.message); // 'GitHub API error: 404 Not Found'
console.log(err.stack); // full stack trace
}
}Every resource that maps to a single entity implements PromiseLike, so you can await it directly or chain methods to access sub-resources:
// Await directly → fetches the resource
const user = await gh.user('octocat');
const repo = await gh.repo('octocat', 'Hello-World');
const pr = await gh.repo('octocat', 'Hello-World').pullRequest(42);
// Chain → access sub-resources without awaiting intermediate objects
const repos = await gh.user('octocat').repos({ sort: 'updated' });
const commits = await gh.repo('octocat', 'Hello-World').pullRequest(42).commits();
const statuses = await gh.org('github').repo('linguist').commit('abc123').statuses();The client uses Bearer token authentication. Supported token types:
- Personal Access Token (PAT) — generate at GitHub → Settings → Developer settings → Personal access tokens
- Fine-grained PAT — recommended for scoped access
- OAuth token — from an OAuth App
- GitHub App installation token — for GitHub App integrations
const gh = new GitHubClient({ token: 'ghp_yourToken' });All domain types are exported:
import type {
// Pagination
GitHubPagedResponse, PaginationParams,
// Client
GitHubClientOptions, RequestEvent, GitHubClientEvents,
// Users & Orgs
GitHubUser, UsersParams, SearchUsersParams, SocialAccount,
GitHubOrganization, OrgMembersParams,
// Repositories
GitHubRepository, ReposParams, ForksParams, SearchReposParams,
// Pull Requests
GitHubPullRequest, GitHubRef, GitHubLabel, GitHubMilestone, PullRequestsParams,
MergeData, MergeResult, UpdatePullRequestData,
GitHubReview, GitHubReviewComment, ReviewsParams, ReviewCommentsParams,
CreateReviewData, AddCommentData, RequestReviewersData,
GitHubPullRequestFile, PullRequestFilesParams,
// Commits
GitHubCommit, GitHubCommitFile, CommitsParams,
GitHubCommitStatus, GitHubCombinedStatus, GitHubCheckRun, GitHubCommitComment,
CommitStatusesParams, CheckRunsParams, CommitCommentsParams,
CreateStatusData, CommitCommentData,
// Branches, Tags, Releases
GitHubBranch, BranchesParams,
GitHubTag, TagsParams,
GitHubRelease, GitHubReleaseAsset, ReleasesParams,
// Webhooks & Content
GitHubWebhook, WebhooksParams,
GitHubContent, ContentParams,
// Events
GitHubEvent, GitHubActor, EventsParams,
// Issues
GitHubIssue, GitHubIssueComment, IssuesParams, CreateIssueData,
// Issue & PR search
SearchIssuesParams,
// Notifications
GitHubNotification, NotificationSubject, NotificationRepository,
NotificationReason, NotificationSubjectType, NotificationsParams,
// GitHub Actions
GitHubWorkflowRun, GitHubWorkflowRunsResponse,
WorkflowRunsParams, WorkflowRunStatus, WorkflowRunConclusion,
// Gists
GitHubGist, GistFile, GistCommit, GistFork, GistComment,
GistsParams, CreateGistData, UpdateGistData, GistCommentData,
// Security advisories
GitHubAdvisory, GitHubAdvisoryVulnerability, AdvisoriesParams,
GitHubRepositoryAdvisory, RepoAdvisoriesParams,
AdvisoryVulnerabilityInput, CreateAdvisoryData, UpdateAdvisoryData,
// GraphQL / Contribution map
ContributionDay, ContributionCalendar, ContributionMapParams,
} from 'gh-api-client';