Guides

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:

src/lib/acme.ts (your code, outside the generated package)
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:

With this split, regeneration never destroys your code. The package directory is disposable, and everything you wrote lives outside it.

On this page