Client & providers
How createClient, provider factories, and the thin-adapter/fat-client split fit together.
repo-sdk is built on a clean split: thin provider adapters translate one provider’s REST API into
a normalized contract, and a single fat client layers all the cross-provider behavior on top —
validation, capability gating, retries, and the listAll iterators.
createClient
createClient takes a provider and returns a RepoClient with grouped namespaces:
import { createClient } from 'repo-sdk';
import { github } from 'repo-sdk/github';
const client = createClient({
provider: github({ auth: { token: process.env.GITHUB_TOKEN! } }),
});
client.namespaces; // list / listAll
client.repos; // list / listAll / get / downloadArchive / getCloneUrl
client.commits; // list / listAll / get
client.tags; // list / listAll
client.webhooks; // create / list / get / update / delete
The client also exposes two read-only fields derived from the provider:
providerName?'github' | 'gitlab' | 'bitbucket' | 'azure-devops'
Which provider backs this client.
'github' | 'gitlab' | 'bitbucket' | 'azure-devops'capabilities?RepoCapabilities
The provider capability object — see Capabilities.
RepoCapabilitiesProvider factories
Each provider ships a factory on its own subpath. A factory validates its options and returns a
RepoProvider — the thin adapter the client drives:
import { github } from 'repo-sdk/github';
import { gitlab } from 'repo-sdk/gitlab';
import { bitbucket } from 'repo-sdk/bitbucket';
import { azureDevOps } from 'repo-sdk/azure-devops';
Every factory takes an auth block; most also accept an optional baseUrl for self-hosted or
enterprise instances. See Authentication for the per-provider auth shapes.
Thin adapter, fat client
The RepoProvider contract is deliberately minimal — a flat list of methods like listRepositories,
getCommit, and createWebhook. Everything that should behave identically across providers lives in
the client, not the adapter:
Parameter validation
Required fields like repo and ref are checked before a request is ever made.
Capability gating
Unsupported options throw unsupported instead of being silently dropped.
Bounded retries
One retry on rate_limited when Retry-After is small (configurable).
Pagination
The listAll async iterators follow cursors for you.
The payoff: a new provider only has to implement the thin adapter, and it instantly inherits all of the client’s behavior.
Injectable fetch
Every factory accepts an optional fetch implementation. In production you omit it and the platform’s
global fetch is used; in tests you inject a stub so no real network call is made:
const provider = github({
auth: { token: 'test-token' },
fetch: async (input, init) => {
// return a Response for the request under test
return new Response(JSON.stringify({ sha: 'abc', commit: { message: 'hi' } }), {
headers: { 'content-type': 'application/json' },
});
},
});
const client = createClient({ provider });
Retry configuration
The client performs one bounded retry on rate_limited when the provider’s Retry-After is small.
Tune it via createClient:
const client = createClient({
provider: github({ auth: { token: process.env.GITHUB_TOKEN! } }),
retry: {
rateLimit: true, // default — retry once on rate_limited
maxRetryAfterSeconds: 10, // default — only if Retry-After ≤ 10s
},
});
Next: Repositories & namespaces
How repositories are addressed and discovered.