hey-api-builders
v2.0.0
Published
Immutable, type-safe test data builders powered by Hey API's generated Faker factories.
Downloads
1,298
Maintainers
Readme
hey-api-builders
hey-api-builders is a custom Hey API plugin
that turns the official @faker-js/faker factories into immutable, type-safe
test-data builders.
Use it when a generated Faker factory is the right source of schema-aware defaults, but a builder is the more readable interface for a test:
const user = new UserBuilder().withEmail('[email protected]').with({ role: 'admin' }).build();Version 2 has one focused job. It does not maintain a second schema interpreter, mocking runtime, or validation layer. Faker generation remains owned by Hey API; this plugin adds typed overrides, ordered transforms, lists, and builders for definitions, operation requests, and operation responses.
Requirements
| Package | Supported version | Why it is needed |
| --------------------- | ----------------- | ----------------------------------------------------------- |
| Node.js | >=22.18.0 | Runs the generator and this plugin |
| @hey-api/openapi-ts | ^0.99.0 | Provides types, the custom-plugin API, and the Faker plugin |
| @faker-js/faker | ^10.0.0 | Used by the generated Faker factories at runtime |
| TypeScript | ^6.0.0 | Required by Hey API at generation time |
| hey-api-builders | ^2.0.0 | Generates the builder module |
The package intentionally tracks a narrow pre-1.0 Hey API range. Hey API's custom-plugin interface is still evolving, so review both projects' release notes before upgrading outside that range.
Like Hey API itself, version 2 is an ESM package.
Installation
Install the generator and builder plugin as development dependencies, then add Faker as a runtime dependency because the generated factory module imports it:
npm install --save-dev @hey-api/openapi-ts@^0.99.0 hey-api-builders@^2.0.0 typescript@^6.0.0
npm install @faker-js/faker@^10.0.0With pnpm:
pnpm add --save-dev @hey-api/openapi-ts@^0.99.0 hey-api-builders@^2.0.0 typescript@^6.0.0
pnpm add @faker-js/faker@^10.0.0If generated Faker code is used only by tests, Faker can be a development dependency too.
Configuration
Declare the TypeScript, Faker, and builder plugins in that order. Keeping the dependencies explicit makes the generated stack and its compatibility target clear:
// openapi-ts.config.ts
import { defineConfig } from '@hey-api/openapi-ts';
import builders from 'hey-api-builders';
export default defineConfig({
input: './openapi.yaml',
output: './src/client',
plugins: [
'@hey-api/typescript',
{
name: '@faker-js/faker',
compatibilityVersion: 10,
},
builders(),
],
});The default export is the canonical API. Named buildersPlugin and
defineConfig aliases are available when a named import better fits your
configuration style.
Run Hey API as usual:
npx openapi-tsThe plugin generates src/client/hey-api-builders.gen.ts. Its exports are also
included in Hey API's generated entry file by default, so either of these import
styles is available:
import { UserBuilder } from './client';
// or
import { UserBuilder } from './client/hey-api-builders.gen';The builder plugin declares the TypeScript and Faker plugins as dependencies,
but @faker-js/faker must still be installed in the consuming project. The
explicit configuration above is recommended because it also fixes Faker's
generated API to compatibility version 10.
Builder plugin options
builders({
// Shared naming case. Category-level `case` values override this.
case: 'PascalCase',
// Each category accepts a boolean, a name string/function, or this object.
definitions: {
enabled: true,
name: '{{name}}Builder',
},
requests: true,
responses: '{{name}}Fixture',
// Re-export generated symbols from the Hey API entry module.
includeInEntry: true,
});| Option | Default | Description |
| ---------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| case | 'PascalCase' | Shared output casing. Supports Hey API's camelCase, PascalCase, preserve, snake_case, and SCREAMING_SNAKE_CASE values. |
| definitions | true | Generate builders for reusable schemas. |
| requests | true | Generate builders for aggregate operation request values. |
| responses | true | Generate builders for operation response factories. |
| includeInEntry | true | Re-export builder symbols from the generated entry file. It can also be a Hey API symbol predicate. |
The three category options accept these equivalent shapes:
builders({
definitions: false,
requests: '{{name}}TestBuilder',
responses: {
enabled: true,
case: 'PascalCase',
name: '{{name}}Builder',
},
});A naming string receives the category's full anchor in {{name}}. A Hey API
NameTransformer function is also accepted when a template is not sufficient.
Faker-specific options such as locale, maxCallDepth, and nameRules belong
on the @faker-js/faker plugin, not builders():
plugins: [
'@hey-api/typescript',
{
name: '@faker-js/faker',
compatibilityVersion: 10,
locale: 'en',
maxCallDepth: 4,
},
builders(),
];Generated builders
Builders are generated only when Hey API produced both the required TypeScript model and Faker factory. Disabling the matching TypeScript or Faker category therefore also removes its builder.
Reusable definitions
A reusable schema named User produces UserBuilder. Object definitions get a
typed convenience method for every direct property:
const user = new UserBuilder({ role: 'viewer' })
.withEmail('[email protected]')
.withDisplayName('Ada')
.build();Property names are normalized to method-safe PascalCase suffixes:
display_name and display-name both become a method based on
withDisplayName. If normalized method names collide, the later method receives
a numeric suffix. The generated TypeScript signature remains indexed to the
original property name.
Primitive and array definitions also receive builders. Their constructor and
with() values replace the generated value as a whole rather than merging it.
Operation requests
An operation with ID getPet produces GetPetRequestBuilder when the official
Faker plugin emits a request factory. The value uses Hey API's aggregate request
shape, with the applicable body, headers, path, and query groups:
const request = new GetPetRequestBuilder()
.withPath({ id: 'pet-42' })
.withQuery({ 'include-history': true })
.build();Only groups present on the operation receive convenience methods. with() is
always available for a typed aggregate patch.
Operation responses
Response builders include the status code in their name. For example, getPet
responses for HTTP 200 and 404 produce GetPetResponse200Builder and
GetPetResponse404Builder:
const found = new GetPetResponse200Builder().with({ id: 'pet-42', name: 'Mochi' }).build();
const missing = new GetPetResponse404Builder()
.with({ code: 'not_found', message: 'No pet found' })
.build();Object response schemas also receive typed property convenience methods when their properties can be resolved from the response schema:
const found = new GetPetResponse200Builder().withName('Mochi').build();with() remains available for every response type. Responses with no status
metadata receive a unique ordinal suffix instead. A response without a Faker
factory does not produce a builder.
Hey API owns symbol conflict resolution. Reserved or colliding model names may therefore be renamed in generated output; import the emitted symbol rather than assuming an unescaped name.
Builder API
Every generated class has the same immutable interface.
new Builder(initial?)
The optional constructor value is the initial typed patch:
const admin = new UserBuilder({ role: 'admin' });For object values, patches are shallow top-level merges. Arrays, primitive values, and nested properties are replaced. Patch values are not deep-cloned or frozen.
.with(patch)
Returns a new builder with a shallow patch merged over the previous patch. The original builder remains usable:
const base = new UserBuilder({ role: 'viewer' });
const admin = base.with({ role: 'admin' });
base.build(); // still has role "viewer"
admin.build(); // has role "admin".with<Property>(value)
Definition, request, and resolvable object-response builders expose typed
convenience methods for known properties or request groups. Each is shorthand
for with() and also returns a new builder:
const named = new UserBuilder().withDisplayName('Ada');.transform(transformer)
Returns a new builder with a transform appended. Transforms run in registration
order after Faker generation and patches, on every build():
const normalized = new UserBuilder().withEmail('[email protected]').transform((user) => ({
...user,
email: user.email.toLowerCase(),
}));The transformer is typed as (value: T) => T. It is trusted at runtime and can
still produce a value that violates the OpenAPI schema.
.build(options?)
Calls the official generated Faker factory, applies the current patch, then applies transforms:
const user = new UserBuilder().withEmail('[email protected]').build({
includeOptional: true,
useDefault: true,
});.buildList(count, options?)
Builds a fresh value count times with the same patch, transforms, and factory
options:
const users = new UserBuilder({ role: 'viewer' }).buildList(10);count must be a safe, non-negative integer. 0 returns an empty array;
negative, fractional, non-finite, and unsafe integer values throw RangeError.
Faker options and deterministic data
build() and buildList() accept the exact option type of their Hey API Faker
factory. The generated module also exports BuilderOptions<TFactory> for code
that needs to derive that first factory argument explicitly.
| Option | Faker default | Meaning |
| ----------------- | ------------------------ | ---------------------------------------------------------------------------------------------- |
| faker | Faker's default instance | A custom Faker instance used by the generated factory. |
| includeOptional | true | Include optional properties. A number from 0 to 1 is the inclusion probability. |
| useDefault | false | Prefer OpenAPI default values. A number from 0 to 1 is the probability of using a default. |
Options are forwarded unchanged. There is intentionally no builder-specific
seed option. Create and seed a Faker instance, then pass it to the build:
import { Faker, en } from '@faker-js/faker';
function createTestFaker(): Faker {
const faker = new Faker({ locale: [en] });
faker.seed(42);
faker.setDefaultRefDate('2026-01-01T00:00:00.000Z');
return faker;
}
const first = new UserBuilder().build({
faker: createTestFaker(),
includeOptional: true,
});
const second = new UserBuilder().build({
faker: createTestFaker(),
includeOptional: true,
});
// `first` and `second` are reproducible because the Faker state starts equal.Passing one seeded instance to buildList() produces a deterministic sequence.
Like Faker itself, a reused instance advances its state after every generated
value.
Composition with validation and request mocking
Builders generate data; they do not validate it. Add Hey API's zod plugin if
tests should assert that final patched or transformed values still satisfy the
schema:
plugins: [
'@hey-api/typescript',
{ name: '@faker-js/faker', compatibilityVersion: 10 },
'zod',
builders(),
];import { UserBuilder, zUser } from './client';
const user = zUser.parse(new UserBuilder().build());For network interception, pair generated data with Hey API's msw plugin or
another mocking library. Network routing is intentionally outside this
package's scope.
Migration from v1
Version 2 is a clean rewrite, not a source-compatible extension of version 1.
- Upgrade Hey API and install Faker 10.
- Add the official
@faker-js/fakerplugin beforebuilders(). - Remove v1-only builder options.
- Regenerate the client and update imports to
hey-api-builders.gen.tsor the generated entry module. - Preserve returned builder instances when calls are not chained; v2 methods do not mutate the original builder.
Before:
builders({
generateZod: true,
mockStrategy: 'runtime',
output: 'builders.gen.ts',
});After:
plugins: [
'@hey-api/typescript',
{ name: '@faker-js/faker', compatibilityVersion: 10 },
'zod', // only when runtime validation is wanted
builders(),
];The following v1 features were removed:
mockStrategy, including the custom runtime, static, and Zod mock strategies;generateZod,useZodForMocks, anduseStaticMocks;- the custom output-name option;
- generated
setOptions()and the v1-specific mock option object; - bundled Zod generation and validation helpers;
- legacy compatibility aliases.
Equivalent schema-aware defaults now come from Hey API's official Faker plugin.
Use Hey API's official Zod plugin for validation. Runtime options now belong on
build() or buildList():
const configured = new UserBuilder().with({ role: 'admin' });
const value = configured.build({
includeOptional: true,
useDefault: true,
});Compatibility and limitations
- The repository's end-to-end suite exercises representative OpenAPI 2.0, 3.0, and 3.1 documents through the real Hey API generator, TypeScript compiler, Faker factories, and generated builders.
- TypeScript 7 is intentionally outside the supported range until Hey API 0.99 no longer relies on compiler APIs removed by that release.
- Schema constraint handling, optional-property probability, default selection, recursion limits, locales, and custom name rules are implemented by Hey API's Faker plugin. This package forwards its factories and options rather than reimplementing them.
- Patches are shallow. Nested objects and arrays are replaced, not deep-merged.
- Builder immutability applies to builder configuration. User-provided objects are not cloned or frozen.
- Overrides and transforms are trusted and are not runtime-validated.
- Convenience
with<Property>()methods are emitted for direct definition properties, aggregate request groups, and response properties that can be resolved from direct schemas, references, or compositions. - Builders are emitted only for definitions, requests, and responses that have the corresponding generated model/factory symbols.
- The generated output filename is owned by the plugin and is not configurable in v2.
- Webhooks, network interception, persistence, and framework fixtures are out of scope.
Contributing
Use Node.js 22.18 or newer and pnpm:
pnpm install
pnpm validate
pnpm pack:checkpnpm validate checks formatting, linting, package types, end-to-end generation
and runtime behavior, coverage, and the distributable build. The end-to-end
tests invoke the public Hey API generator and compile the complete generated
tree; they do not mock the plugin API.
Bug reports should include the OpenAPI fragment, generated builder name, and
exact versions of Node.js, @hey-api/openapi-ts, @faker-js/faker, and this
package. Open an issue or pull request in the
GitHub repository.
Releases
See CHANGELOG.md for user-visible changes. Maintainer releases
use a v<package-version> GitHub release tag; publishing runs from the tagged
commit after the repository validation gates pass.
