@molecule/api-git-provider
v1.0.3
Published
Abstract interface for a git hosting provider — OAuth endpoints, token auth shape, repository listing and lookup — so an app can support GitHub, GitLab, Gitea or any other host without naming one.
Readme
@molecule/api-git-provider
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
@molecule/api-git-provider — the abstract interface for a git hosting
provider: OAuth endpoints, the token auth shape a push expects, and
repository listing/lookup.
This exists because "which git hosts do we support?" kept being answered by a closed union inside the application:
const GIT_PROVIDERS = ['github', 'gitlab'] as const
const PROVIDER_DEFAULTS: Record<GitProvider, …> // OAuth URLs + scopes
const TOKEN_USERNAME: Record<string, string> // x-access-token | oauth2
const DEFAULT_PROVIDER_HOSTS: Record<GitProvider, …> // github.com | gitlab.com
provider === 'github' ? `${base}/repos/${path}` : `${base}/projects/${…}`Four parallel tables and a scatter of ternaries, all keyed off that union, so adding a host meant editing the app. Behind this interface the supported set becomes whichever bonds are wired, and a consumer never names a vendor.
Quick Start
import {
listGitProviders,
registerGitProvider,
requireGitProvider,
} from '@molecule/api-git-provider'
import { provider as github } from '@molecule/api-git-provider-github'
import { provider as gitea } from '@molecule/api-git-provider-gitea'
registerGitProvider(github)
registerGitProvider(gitea)
// What a "connect your repo" picker offers — not a hardcoded list.
listGitProviders().map((p) => ({ id: p.id, label: p.label }))
const p = requireGitProvider('github')
const repos = await p.listRepositories({
host: p.defaultHost,
token: '<oauth token>',
page: 1,
perPage: 30,
})Type
core
Installation
npm install @molecule/api-git-providerAPI
Interfaces
GetRepositoryInput
Input for a single repository lookup.
interface GetRepositoryInput {
/** Host to query. */
host: string
/** OAuth access token, or null for an unauthenticated (public) lookup. */
token: string | null
/** `owner/name` path. */
path: string
}GitProvider
A git hosting provider.
Everything here was a per-vendor branch or lookup table inside an
application before this interface existed: provider === 'github' ? … : …
for API bases, headers, list endpoints and response shapes, plus four
parallel Record<GitProvider, …> tables keyed off a closed union. Adding a
host meant editing the app. A provider bond absorbs all of it, so the set of
supported hosts becomes "whichever bonds are wired".
interface GitProvider {
/** Stable identifier, e.g. `github`. Used as the bond name and in stored credentials. */
id: string
/** Human-readable name for pickers, e.g. `GitHub`. */
label: string
/**
* The public host this provider lives on, e.g. `github.com`.
*
* Load-bearing for security, not just defaults: an OAuth token that grants
* repo read/write must never be embedded in a remote URL for an arbitrary
* user-supplied host, so a consumer binds tokens to this host (plus any
* configured self-hosted endpoint) and refuses everything else.
*/
defaultHost: string
/** How this provider authenticates — OAuth flow, or a user-minted token. */
auth: GitProviderAuth
/**
* HTTPS basic-auth username to pair with the token as the password, or NULL
* when the provider expects the account's own username.
*
* Nullable because it is not always a per-provider constant: GitHub wants the
* literal `x-access-token` and GitLab `oauth2`, but SmolForge wants the
* user's Forge username — a per-CREDENTIAL value the provider cannot know.
* A consumer that finds null must substitute the connected account's
* username. The wrong username fails as an opaque 401 at push time, nowhere
* near the code that chose it.
*/
basicAuthUsername: string | null
/**
* REST API base URL for a host.
*
* A parameter rather than a constant because the same provider serves a
* different base for its public host than for a self-hosted instance —
* `api.github.com` vs `<host>/api/v3`.
*
* @param host - The host being addressed.
* @returns The API base URL, without a trailing slash.
*/
apiBaseForHost(host: string): string
/**
* Headers for an API call, including auth when a token is given.
*
* @param token - OAuth access token, or null for unauthenticated requests.
* @returns Headers to send.
*/
apiHeaders(token: string | null): Record<string, string>
/**
* List repositories the token can see, newest activity first.
*
* @param input - Host, token and pagination.
* @returns Normalized repositories. Empty array when the page is past the end.
*/
listRepositories(input: ListRepositoriesInput): Promise<GitRepository[]>
/**
* Look up one repository.
*
* @param input - Host, token and `owner/name` path.
* @returns The repository, or null when it does not exist or is not visible.
*/
getRepository(input: GetRepositoryInput): Promise<GitRepository | null>
}GitProviderOAuth
OAuth endpoints and scope for a provider.
interface GitProviderOAuth {
/** Authorization endpoint the user is redirected to. */
authorizeUrl: string
/** Token exchange endpoint. */
tokenUrl: string
/**
* Space-separated scopes.
*
* State what each scope is FOR. GitLab needs `read_api` on top of the
* `*_repository` scopes because the repository scopes cover only the git
* protocol, not the REST API a repo picker calls — an omission that fails
* only at the picker, long after the OAuth flow looks successful.
*/
scope: string
}GitRepository
A repository as this app understands it, independent of whose API described
it. GitHub calls it full_name/clone_url, GitLab path_with_namespace/
http_url_to_repo; a consumer should never have to know which.
interface GitRepository {
/** `owner/name`, however the provider spells it. */
fullName: string
/** HTTPS clone URL. */
url: string
/** Whether the repository is private. Null when the provider does not say. */
private: boolean | null
/** Default branch name, or null when the provider does not report one. */
defaultBranch: string | null
/** Approximate size in KB, or null when unknown. */
sizeKb: number | null
/** ISO 8601 timestamp of the last push/activity, or null. */
updatedAt: string | null
/** Short description, or null. */
description: string | null
}ListRepositoriesInput
Input for a paginated repository listing.
interface ListRepositoriesInput {
/** Host to query — the provider's default, or a self-hosted instance. */
host: string
/** OAuth access token. */
token: string
/** 1-based page number. */
page: number
/** Page size. */
perPage: number
}Types
GitProviderAuth
How a provider authenticates.
A discriminated union rather than an optional OAuth block, because the first
non-OAuth provider proved the difference is structural, not a missing field.
SmolForge has no authorize/token endpoints at all — the user mints a personal
access token and uses it directly. Modelling that as "OAuth with empty URLs"
would let a consumer start an authorize redirect to "".
type GitProviderAuth =
| ({ kind: 'oauth' } & GitProviderOAuth)
| {
kind: 'pat'
/** Where the user creates a token, for the UI to link to. */
tokensUrl?: string
}Functions
clearGitProviders()
Remove every registered provider. Test teardown only.
function clearGitProviders(): voidgetGitProvider(id)
Look up a registered provider.
function getGitProvider(id: string): GitProvider | undefinedid— The provider id, e.g.github.
Returns: The provider, or undefined when it is not wired.
hasGitProvider(id)
Whether a provider id is wired.
function hasGitProvider(id: string): booleanid— The provider id.
Returns: True when registered.
listGitProviders()
Every registered provider, sorted by id.
This is what a UI should render as the list of connectable hosts — the set is whatever the deployment wired, never a hardcoded union.
function listGitProviders(): GitProvider[]Returns: The registered providers.
registerGitProvider(provider)
Register a git provider. Re-registering the same id replaces it.
function registerGitProvider(provider: GitProvider): voidprovider— The provider to register.
requireGitProvider(id)
Look up a provider, throwing when it is absent.
Use this on a path where a missing provider is a configuration error rather than a branch: the error names what IS wired, because "unknown provider gitlab" is unactionable while "gitlab is not wired; github is" says exactly what to do.
function requireGitProvider(id: string): GitProviderid— The provider id.
Returns: The provider.
Available Providers
| Provider | Package |
| --------- | -------------------------------------- |
| Gitea | @molecule/api-git-provider-gitea |
| GitHub | @molecule/api-git-provider-github |
| GitLab | @molecule/api-git-provider-gitlab |
| SmolForge | @molecule/api-git-provider-smolforge |
Injection Notes
- Named multi-provider, like
ai— not a singleton. One deployment has several wired at once because different users connect different hosts. Register by id and look up by id; there is no "current" git provider. defaultHostis a security boundary, not a default. An OAuth token here grants repository read/write, so it must never be embedded in a remote URL for an arbitrary user-supplied host. Bind tokens todefaultHostplus any configured self-hosted endpoint, and refuse the rest.tokenUsernameis not cosmetic. Pushing over HTTPS with a token as the password needs the username the provider expects (x-access-tokenfor GitHub,oauth2for GitLab). The wrong one fails as an opaque auth error at push time, nowhere near the OAuth code that chose it.apiBaseForHosttakes the host because the same provider serves a different base for its public host than for a self-hosted instance (api.github.comvs<host>/api/v3). A constant cannot express that.listRepositoriesreturns[]past the last page, never an error — a caller paginating until empty is the normal shape, and a throw there turns an ordinary end-of-list into a failed import.
