CLI tool
oazapfts/oazapfts avatar
oazapfts/oazapfts

oazapfts generates a TypeScript API client from an OpenAPI spec, via the AST

Generate TypeScript clients to tap into OpenAPI servers

579 stars89 forksTypeScriptLicense varies

At a glance

What is it?
oazapfts is a TypeScript client generator for OpenAPI and Swagger servers that builds an abstract syntax tree and prints it, rather than filling in templates, and emits a single tree-shakeable file per API. It suits teams that want typed fetch calls with a discriminated union per status code, and its runtime was split out into a separate package at version 6.0.0.
Who is it for?
Adopt oazapfts if you want a typed TypeScript client for an OpenAPI service and prefer discriminated unions per status code over a loosely typed fetch wrapper, since the generated functions expose a status and typed data and can be narrowed with a type guard.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 80 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

AST output instead of string templates

oazapfts generates TypeScript clients to tap into OpenAPI servers, and the distinctive claim in the feature list is that it is AST-based.

The README contrasts this with other code generators: oazapfts does not use templates to generate code but uses TypeScript's built-in API to generate and pretty-print an abstract syntax tree. The practical consequence is that the output is a real TypeScript program tree rather than text with holes in it, which is what makes the formatting stable and the generated code something a formatter can work on.

The speed claim follows from the same decision. The CLI does not use any of the common Java-based tooling, so the code generation is described as super fast. That matters for a generator that runs in a build step or a git hook.

Two more properties come from the same design. Everything is emitted as a single file, with all functions and types co-located in one self-contained file, and each function is individually exported so you can bundle only the ones you actually use, which the README calls tree-shakeable.

The last feature is the one that shows up in your code. The generated API methods do not leak any HTTP-specific implementation details, and the example given is that all optional parameters are grouped together in one object, no matter whether they end up in the headers, the path or the query string.

The CLI takes a spec URL or path and a dozen flags

Installation is a single npm package:

bash
npm install oazapfts

The CLI signature is oazapfts <spec> [filename], where the spec is the URL or local path of an OpenAPI or Swagger spec in either json or yml, and the filename is where the .ts file is written. If the filename is omitted, the code goes to stdout, which is the mode to use if you want to pipe it through a formatter.

The options list is short and each one is a real decision:

bash
oazapfts <spec> [filename]

Options:
--exclude, -e tag to exclude
--include, -i tag to include
--optimistic
--useEnumType
--useUnknown
--mergeReadWriteOnly
--argumentStyle=<positional | object> (default: positional)
--allSchemas
--futureStripLegacyMethods
--numericBooleanQueryParameters

The include and exclude flags filter by tag, so you can generate a client for part of a large spec. useEnumType generates enums instead of union types, and useUnknown generates unknown instead of any for empty schemas, which is the difference between a type error you can find and one you cannot. useAllSchemas generates types for all schemas in the spec. mergeReadWriteOnly merges the separate read-only and write-only types the generator would otherwise create.

The argumentStyle flag is the one to think about. By default the parameters are separate positional arguments, and with object they become a single object argument for parameters and request body. numericBooleanQueryParameters serializes boolean query parameters as 1 or 0 instead of true or false.

Two names per operation, and the flag that removes the old one

For each operation in the spec, the generated API exports a function whose name matches the operationId, and if no ID is specified a reasonable name is generated from the HTTP verb and the path.

There is a compatibility wrinkle that the README explains in some detail. By default, when an operationId contains special characters such as dots or colons, for example scope1.userAccount.get, oazapfts generates both a normalized method name, scope1UserAccountGet, and a deprecated fallback using the HTTP verb and the path, getUsersById, for backward compatibility. The --futureStripLegacyMethods flag skips generating the deprecated aliases, and the README says the next major version will default to that behaviour.

That is a useful thing to know before you generate, because it affects your diff. A spec change that renames an operationId in a way that produces a normalized name will leave the old fallback in your generated file until you pass the flag, and then the file changes shape on a version bump rather than on a spec change.

The other detail is how you import. The README notes that if your API is large and you want to take advantage of tree-shaking to exclude unused code, use individual named imports instead of importing the whole namespace.

Explicit mode gives you a discriminated union per status code

The response handling comes in two modes, an explicit mode which is the default and an optimistic mode which makes the response handling less verbose.

In explicit mode, each function returns an ApiResponse object that exposes the status code, the response headers and the data. Since an operation can return different types depending on the status code, the actual return type is a union of all possible responses, discriminated by their status. The generated function for the petstore example returns a union of a 200 with a Pet, a 400 with a string and a 404 with nothing.

Because the data property is typed as Pet|string in that case, you narrow it with a type guard:

ts
const res = await api.getPetById(1);
if (res.status === 200) {
  const pet = res.data;
  // pet is properly typed as Pet
}

The README also has a shorter path, the handle helper, which is imported from @oazapfts/runtime and takes a handler per status code. The helper throws an HttpError for any unhandled status code, unless you add a default handler, which takes the status and the data.

The mode choice is a real design decision rather than a preference. The README says explicit mode is best suited to APIs that return different types for different response codes, or APIs where you need the response headers as well as the body, and that if your API is simple and you do not need that flexibility, the optimistic mode is worth considering.

The runtime became a separate package, and the repo is three workspaces

The most consequential change to this generator is recorded in a note under the installation section. With version 3.0.0, oazapfts became a runtime dependency, and the generated code no longer includes all the fetch logic. As of 6.0.0 the runtime was moved to a separate package, @oazapfts/runtime.

So the generated file is not self-contained any more, despite the single file feature. It imports from the runtime package, which is where the handle helper and the RequestOpts type come from, and the README links the runtime source for the RequestOpts definition.

The fetch options are the last argument of every generated function, an optional object that can pass options to the fetch call, for example additional headers or an AbortSignal to cancel the request later. The same object can override the default baseUrl or supply a custom fetch implementation. The README suggests that instead of passing options on every call, you can overwrite the global defaults.

The repository is a pnpm workspace with three packages, packages/codegen, packages/runtime and packages/resolve, and the root scripts show the shape of the development loop. The build runs the three packages in dependency order, the test script runs unit tests then end to end tests, and the end to end tests generate the demo clients and run them against a server started with open-api-mocker against the petstore spec. There is also a check that fails if the generated demo output differs from what is committed, which is the guard against the generated code drifting from the generator.

The demo directory is worth reading before you generate your own. It contains the petstore.json spec, the generated api.ts, and separate generated files for each option combination, including optimisticApi.ts, enumApi.ts, mergedReadWriteApi.ts and objectStyleArgument.ts, alongside api.test.ts.

Editorial conclusion

Adopt oazapfts if you want a typed TypeScript client for an OpenAPI service and prefer discriminated unions per status code over a loosely typed fetch wrapper, since the generated functions expose a status and typed data and can be narrowed with a type guard. Do not adopt it if you need the generated file to be dependency free, because since version 3.0.0 the output no longer includes the fetch logic and since 6.0.0 the runtime lives in the separate @oazapfts/runtime package, which the handle helper comes from. Before you commit, run the generator against your own spec and read the output rather than the demo, decide between the default explicit mode and the optimistic mode, check the --argumentStyle flag if your API has many optional parameters, and note the release picture, where 7.5.0 shipped on 2026-03-20 and the last push was on 2026-08-04.

Frequently asked questions

How do I install and run oazapfts?

Install the package with npm install oazapfts, then run oazapfts <spec> [filename] where the spec is a URL or local path of an OpenAPI or Swagger spec in json or yml. If the filename is omitted the generated code is written to stdout, and the options list covers tag filtering, --useEnumType, --useUnknown, --mergeReadWriteOnly, --argumentStyle, --allSchemas and --optimistic.

What is the difference between explicit and optimistic mode in oazapfts?

In the default explicit mode each function returns an ApiResponse with a status, headers and typed data, and the return type is a union discriminated by status. The optimistic mode makes the response handling less verbose, and the README recommends it when the API is simple and you do not need the headers or the per-status flexibility.

Why does the code generated by oazapfts need a runtime package?

Since version 3.0.0 the generated code no longer includes all the fetch logic, and as of 6.0.0 the runtime was moved to a separate package, @oazapfts/runtime, which is also where the handle helper and the RequestOpts type live.

How do I handle different response types for the same operation in oazapfts?

In explicit mode the generated function returns a union discriminated by status, and you narrow it with a type guard on res.status, or you use the handle helper from @oazapfts/runtime which takes a handler per status code and throws an HttpError for unhandled statuses unless you add a default handler.

What does the --futureStripLegacyMethods flag do in oazapfts?

By default, when an operationId contains special characters such as dots or colons, the generator emits a normalized method name and a deprecated fallback derived from the HTTP verb and path. The flag skips the deprecated aliases, and the README says the next major version will default to that behaviour.

Official sources

  1. Issues
  2. oazapfts/oazapfts on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/oazapfts-oazapfts.svg)](https://hysenlabs.com/projects/oazapfts-oazapfts)

Was this useful?