Corsair
PluginsGitHub

GitHub Database Schema

Database entities and querying synced GitHub data

The GitHub plugin automatically syncs data from GitHub to your database. This allows you to query GitHub data without making API calls, providing faster access and offline capabilities.

New to Corsair? Learn about core concepts like database operations, data synchronization, and multi-tenancy before working with the database.

Full Implementation: For the complete, up-to-date database schema and implementations, see the GitHub plugin source code on GitHub.

Synced Entities

The GitHub plugin syncs the following entities to your database:

  • users - GitHub user profiles
  • repositories - Repository information and metadata
  • issues - Issues and their comments
  • pullRequests - Pull requests and reviews
  • releases - Releases and release assets
  • workflows - GitHub Actions workflows

Database API

Access synced data through the db property:

const repos = await corsair.github.db.repositories.search({
    data: { name: "Hello-World" },
});

Search Options

All entity searches support the following options:

await corsair.github.db.repositories.search({
    data: { name: "Hello-World" },
    limit: 100,
    offset: 0,
});

Users

User profiles are synced when you fetch issues, pull requests, or repositories.

Schema

{
    id: number;
    login: string;
    nodeId?: string;
    avatarUrl?: string;
    gravatarId?: string | null;
    url?: string;
    htmlUrl?: string;
    type?: string;
    siteAdmin?: boolean;
    name?: string | null;
    email?: string | null;
    createdAt?: Date | null;
    updatedAt?: Date | null;
}

Querying Users

Search by login:

const users = await corsair.github.db.users.search({
    data: { login: "octocat" },
});

Search by email:

const users = await corsair.github.db.users.search({
    data: { email: "octocat@github.com" },
});

Repositories

Repositories are synced when you list or get repositories.

Schema

{
    id: number;
    nodeId?: string;
    name: string;
    fullName?: string;
    private?: boolean;
    htmlUrl?: string;
    description?: string | null;
    fork?: boolean;
    url?: string;
    createdAt?: Date | null;
    updatedAt?: Date | null;
    pushedAt?: Date | null;
    defaultBranch?: string;
    language?: string | null;
    stargazersCount?: number;
    watchersCount?: number;
    forksCount?: number;
    openIssuesCount?: number;
    archived?: boolean;
    disabled?: boolean;
}

Querying Repositories

Search by name:

const repos = await corsair.github.db.repositories.search({
    data: { name: "Hello-World" },
});

Search by full name:

const repos = await corsair.github.db.repositories.search({
    data: { fullName: "octocat/Hello-World" },
});

Find private repositories:

const privateRepos = await corsair.github.db.repositories.search({
    data: { private: true },
});

Find archived repositories:

const archivedRepos = await corsair.github.db.repositories.search({
    data: { archived: true },
});

Filter by language:

const jsRepos = await corsair.github.db.repositories.search({
    data: { language: "JavaScript" },
});

Example: Get Repository by Name

async function getRepository(owner: string, name: string) {
    await corsair.github.api.repositories.get({
        owner,
        repo: name,
    });
    
    const repos = await corsair.github.db.repositories.search({
        data: { name, fullName: `${owner}/${name}` },
    });
    
    return repos[0] || null;
}

Issues

Issues are synced when you list, get, create, or update issues.

Schema

{
    id: number;
    nodeId?: string;
    url?: string;
    repositoryUrl?: string;
    labelsUrl?: string;
    commentsUrl?: string;
    eventsUrl?: string;
    htmlUrl?: string;
    number: number;
    state: string;
    stateReason?: 'completed' | 'reopened' | 'not_planned' | 'duplicate' | null;
    title: string;
    body?: string | null;
    user?: GithubUser | null;
    labels?: Array<any>;
    assignee?: GithubUser | null;
    assignees?: Array<GithubUser> | null;
    locked?: boolean;
    comments?: number;
    createdAt?: Date | null;
    updatedAt?: Date | null;
    closedAt?: Date | null;
}

Querying Issues

Search by state:

const openIssues = await corsair.github.db.issues.search({
    data: { state: "open" },
});

Search by number:

const issue = await corsair.github.db.issues.search({
    data: { number: 1 },
});

Find locked issues:

const lockedIssues = await corsair.github.db.issues.search({
    data: { locked: true },
});

Example: Get Recent Issues

async function getRecentIssues(owner: string, repo: string) {
    await corsair.github.api.issues.list({
        owner,
        repo,
        state: "open",
        perPage: 50,
    });
    
    const issues = await corsair.github.db.issues.search({
        data: {},
        limit: 50,
    });
    
    return issues.sort((a, b) => 
        (b.updatedAt?.getTime() || 0) - (a.updatedAt?.getTime() || 0)
    );
}

Pull Requests

Pull requests are synced when you list, get, or receive pull request webhooks.

Schema

{
    id: number;
    nodeId?: string;
    url?: string;
    htmlUrl?: string;
    diffUrl?: string;
    patchUrl?: string;
    issueUrl?: string;
    number: number;
    state: 'open' | 'closed';
    locked?: boolean;
    title: string;
    user?: GithubUser;
    body?: string | null;
    createdAt?: Date | null;
    updatedAt?: Date | null;
    closedAt?: Date | null;
    mergedAt?: Date | null;
    mergeCommitSha?: string | null;
    assignee?: GithubUser | null;
    assignees?: Array<GithubUser> | null;
    draft?: boolean;
    merged?: boolean;
    mergeable?: boolean | null;
    comments?: number;
    reviewComments?: number;
    commits?: number;
    additions?: number;
    deletions?: number;
    changedFiles?: number;
}

Querying Pull Requests

Search by state:

const openPRs = await corsair.github.db.pullRequests.search({
    data: { state: "open" },
});

Search by number:

const pr = await corsair.github.db.pullRequests.search({
    data: { number: 1 },
});

Find merged pull requests:

const mergedPRs = await corsair.github.db.pullRequests.search({
    data: { merged: true },
});

Find draft pull requests:

const draftPRs = await corsair.github.db.pullRequests.search({
    data: { draft: true },
});

Example: Get Pull Request by Number

async function getPullRequest(owner: string, repo: string, number: number) {
    await corsair.github.api.pullRequests.get({
        owner,
        repo,
        pullNumber: number,
    });
    
    const prs = await corsair.github.db.pullRequests.search({
        data: { number },
    });
    
    return prs[0] || null;
}

Releases

Releases are synced when you get or create releases.

Schema

{
    id: number;
    nodeId?: string;
    url?: string;
    htmlUrl?: string;
    assetsUrl?: string;
    uploadUrl?: string;
    tarballUrl?: string | null;
    zipballUrl?: string | null;
    tagName?: string;
    targetCommitish?: string;
    name?: string | null;
    body?: string | null;
    draft?: boolean;
    prerelease?: boolean;
    createdAt?: Date | null;
    publishedAt?: Date | null;
    author?: GithubUser;
}

Querying Releases

Search by tag name:

const releases = await corsair.github.db.releases.search({
    data: { tagName: "v1.0.0" },
});

Find draft releases:

const draftReleases = await corsair.github.db.releases.search({
    data: { draft: true },
});

Find prereleases:

const prereleases = await corsair.github.db.releases.search({
    data: { prerelease: true },
});

Workflows

Workflows are synced when you list or get workflows.

Schema

{
    id: number;
    nodeId?: string;
    name: string;
    path: string;
    state: 'active' | 'deleted' | 'disabled_fork' | 'disabled_inactivity' | 'disabled_manually';
    url?: string;
    htmlUrl?: string;
    badgeUrl?: string;
    createdAt?: Date | null;
    updatedAt?: Date | null;
    deletedAt?: Date | null;
}

Querying Workflows

Search by name:

const workflows = await corsair.github.db.workflows.search({
    data: { name: "CI" },
});

Find active workflows:

const activeWorkflows = await corsair.github.db.workflows.search({
    data: { state: "active" },
});

Multi-Tenancy

When using multi-tenancy, all database queries are automatically scoped to the tenant:

const tenant = corsair.withTenant("tenant-id");

const repos = await tenant.github.db.repositories.search({
    data: { name: "Hello-World" },
});

await tenant.github.api.repositories.get({
    owner: "octocat",
    repo: "Hello-World",
});

Each tenant's data is isolated in the database.


Data Synchronization

When Data is Synced

Data is automatically synced to your database in the following scenarios:

  1. API Calls - When you call API endpoints, responses are synced
  2. Webhooks - When webhooks are received, data is updated
  3. Automatic Updates - Webhook events trigger updates for related entities

Manual Syncing

You can trigger syncs manually by calling API endpoints:

await corsair.github.api.repositories.list({
    owner: "octocat",
});

await corsair.github.api.issues.list({
    owner: "octocat",
    repo: "Hello-World",
});

await corsair.github.api.pullRequests.list({
    owner: "octocat",
    repo: "Hello-World",
});

Data Freshness

Database data reflects the last sync time. For real-time accuracy, consider:

  • Webhooks - Set up webhooks for real-time updates
  • Periodic Syncing - Schedule API calls to refresh data
  • On-Demand - Fetch from API when you need the latest data
async function getLatestRepository(owner: string, repo: string) {
    const apiResult = await corsair.github.api.repositories.get({
        owner,
        repo,
    });
    
    const dbRepos = await corsair.github.db.repositories.search({
        data: { name: repo, fullName: `${owner}/${repo}` },
    });
    
    return dbRepos[0];
}

Best Practices

1. Check Database First

Always check the database before making API calls:

let repos = await corsair.github.db.repositories.search({
    data: { name: "Hello-World" },
});

if (!repos[0]) {
    await corsair.github.api.repositories.list({
        owner: "octocat",
    });
    repos = await corsair.github.db.repositories.search({
        data: { name: "Hello-World" },
    });
}

2. Use Webhooks for Real-Time Updates

Configure webhooks to keep data fresh automatically:

github({
    webhookHooks: {
        pullRequestOpened: {
            after: async (ctx, result) => {
                console.log("Pull request synced:", result.data.pull_request.title);
            },
        },
    },
})

3. Handle Missing Data

Always handle cases where data might not exist:

async function getRepositoryOrThrow(owner: string, name: string) {
    const repos = await corsair.github.db.repositories.search({
        data: { name, fullName: `${owner}/${name}` },
    });
    
    if (!repos[0]) {
        throw new Error(`Repository ${owner}/${name} not found`);
    }
    
    return repos[0];
}

See Database for more information about database concepts and querying patterns.