Extend the client
The generated package is replaced whole on every regeneration. Put your configuration, logging, and domain logic in modules you own that wrap the client.
The generated package is wholly owned by the generator. Regeneration replaces the directory, and anything you wrote inside it is gone. That is a feature. It is what makes the next pull request a clean diff of spec changes and nothing else.
Customization lives in code you own that wraps the client. The client was designed to make that easy.
A factory you own
Pin the base URL, auth, and defaults in one place so call sites never repeat configuration:
import { AcmeClient } from "acme";
export function createAcmeClient(): AcmeClient {
return new AcmeClient({
baseUrl: process.env.ACME_BASE_URL,
bearerToken: process.env.ACME_TOKEN!,
defaultHeaders: { "Request-Source": "core-team" },
});
}Instrument with hooks and fetch
Logging, metrics, tracing, and proxies belong in the fetch option or the hooks, not in generated files:
const client = new AcmeClient({
bearerToken: token,
fetch: async (input, init) => {
const started = Date.now();
const response = await fetch(input, init);
console.log(`${init?.method ?? "GET"} ${String(input)} ${response.status} ${Date.now() - started}ms`);
return response;
},
onRequest: (context) => {
context.headers["Trace-Id"] = crypto.randomUUID();
},
});Python takes transport=, on_request=, and friends. Go takes WithHTTPClient and WithOnRequest. See Hooks and debug logging.
Domain logic on top
Caching, mapping to your own types, and business rules belong in your functions that call the client and pass results on:
import { unwrap } from "acme";
import { createAcmeClient } from "./acme";
const client = createAcmeClient();
export async function activeAccountIds(): Promise<string[]> {
const ids: string[] = [];
for await (const account of client.accounts.list({ limit: 100 })) {
if (account.status === "active") ids.push(account.id);
}
return ids;
}Things that belong in the project, not in code
Some customizations change what gets generated. Those live in the project's config so every regeneration carries them:
- Parameters every call should carry: global parameters.
- Which statuses to retry and how: retry tuning.
- How an unusual endpoint pages: pagination rules.
- A wrong type or name in a spec you cannot edit: spec patches.
With this split, regeneration never destroys your code. The package directory is disposable, and everything you wrote lives outside it.
Publish your packages
Publish generated packages to npm, PyPI, and as a Go module under your own name. The packages are yours. typeship maintains them; you own the registry.
Generate in CI
Drive typeship from a pipeline with the typeship CLI or API: regenerate a project on your schedule, or generate a package ad hoc and commit it yourself.