Skip to content
Draft
192 changes: 192 additions & 0 deletions docs/content/docs/guides/angular.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ supported as a backward-compatible alias.
By default, the Angular client generates injectable service classes backed by
`HttpClient`.

If you also generate MSW/Faker mocks and want them independently importable
in Node without ever evaluating Angular modules, see
[Runtime-safe artifact groups](#runtime-safe-artifact-groups) below.

Available Angular retrieval modes:

- `httpClient` — keep retrievals as injectable service methods backed by `HttpClient`
Expand Down Expand Up @@ -126,6 +130,188 @@ Quick rule of thumb:
- if you do not set `override.angular.retrievalClient`, Orval defaults to `httpClient`
- create, update, delete, and other mutation-style operations still use `HttpClient` methods by default

## Runtime-safe artifact groups

One OpenAPI document generates code that runs in (at least) three different
runtimes:

| Group | Runs in | Files |
|---|---|---|
| Schemas | Universal (browser + Node) | Plain TS types or Zod schemas |
| Client (`HttpClient` / `httpResource`) | Browser (Angular) | `*.service.ts`, `*.resource.ts` |
| Mocks (MSW / Faker) | Node (tests, Storybook, contract tests) | `*.msw.ts`, `*.faker.ts` |

By default Orval writes all of these into one tree with no dedicated entry
point, so consumers often hand-roll a root barrel (`export * from
'./generated'`) to get a single import. That barrel re-exports everything —
including the Angular service classes — so a Node-only MSW consumer (a
Vitest/Jest setup file, a contract-test runner, Storybook's Node-side loaders)
transitively evaluates `@angular/core` / `@angular/common/http` the moment it
imports the mock handlers, which can crash outside a real Angular platform.

You can get most of the way there today with options that already exist,
independently of each other:

- `output.schemas` — put schemas in their own directory; Orval emits a
dedicated `index.ts` barrel there
- `mock.generators[].path` — give the `msw` and `faker` generators their own
directories instead of inlining mocks into the client files
- `mock.indexMockFiles: true` — emit `index.msw.ts` / `index.faker.ts`
barrels at each mock directory's root

```ts title="orval.config.ts"
import { defineConfig } from 'orval';

export default defineConfig({
petstore: {
output: {
mode: 'tags-split',
target: 'src/generated/client/petstore.ts',
schemas: 'src/generated/schemas',
client: 'angular',
tagsSplitDeduplication: true,
override: {
angular: {
retrievalClient: 'both',
},
},
mock: {
indexMockFiles: true,
generators: [
{ type: 'msw', path: 'src/generated/msw' },
{ type: 'faker', path: 'src/generated/faker' },
],
},
},
input: {
target: './petstore.yaml',
},
},
});
```

This already gives you a Node-safe `msw/index.msw.ts` and `faker/index.faker.ts`
(both import only `msw`/`@faker-js/faker` plus type-only schema imports) and a
universal `schemas/index.ts`. The one piece it doesn't give you is a
**client** barrel — in `tags-split` mode there's no generated entry point that
re-exports every `*.service.ts` (and, in `both` mode, the sibling
`*.resource.ts`) file, so you're left hand-writing one yourself.

`output.artifacts` is ergonomic sugar over exactly the primitives above, plus
that missing client barrel:

- `artifacts.schemas` → normalizes onto `output.schemas`
- `artifacts.msw` / `artifacts.faker` → normalize onto the matching
`mock.generators[].path` and force `mock.indexMockFiles: true`
- `artifacts.client` (optional; defaults to `output.target`'s directory) → a
new `<clientDir>/index.ts` barrel, re-exporting every `*.service.ts` and
`*.resource.ts` file actually written for the client group (built from the
files Orval wrote, never guessed from tag names)

The equivalent config, using `artifacts`:

```ts title="orval.config.ts"
import { defineConfig } from 'orval';

export default defineConfig({
petstore: {
output: {
mode: 'tags-split',
target: 'src/generated/client/petstore.ts',
client: 'angular',
tagsSplitDeduplication: true,
override: {
angular: {
retrievalClient: 'both',
},
},
artifacts: {
schemas: 'src/generated/schemas',
msw: 'src/generated/msw',
faker: 'src/generated/faker',
},
},
input: {
target: './petstore.yaml',
},
},
});
```

Both configs generate identical `schemas/`, `msw/`, and `faker/` trees. The
`artifacts` version additionally emits `client/index.ts`:

```
src/generated/
├── schemas/
│ ├── pet.ts
│ ├── ...
│ └── index.ts
├── client/
│ ├── pets/
│ │ ├── pets.service.ts
│ │ └── pets.resource.ts
│ ├── common-types.ts
│ └── index.ts
├── msw/
│ ├── pets/pets.msw.ts
│ └── index.msw.ts
└── faker/
├── pets/pets.faker.ts
└── index.faker.ts
```

In a monorepo, map each barrel to its own package `exports` entry so hosts
import only what they need:

```json title="package.json (data-access lib)"
{
"exports": {
".": "./src/generated/client/index.ts",
"./msw": "./src/generated/msw/index.msw.ts",
"./faker": "./src/generated/faker/index.faker.ts"
}
}
```

```ts
// browser code — only pulls in Angular + schema types
import { PetsService } from '@acme/petstore';
import { listPetsResource } from '@acme/petstore';

// Node test setup — never evaluates Angular
import { getPetsMock } from '@acme/petstore/msw';
import type { Pet } from '@acme/petstore/schemas';

// a single operation's handler, imported straight from its tag file
import { getShowPetByIdMockHandler } from '@acme/petstore/msw/pets/pets.msw';
```

Wiring `exports`/Nx project boundaries to physically separate packages is a
host-project concern — Orval's job ends at emitting the group directories and
barrels above.

<Callout>
**Caveats**

- `output.artifacts` requires `mode: 'tags-split'` and `indexFiles` to stay
enabled (its default) — Orval throws a clear error otherwise.
- Declaring `artifacts.msw` / `artifacts.faker` forces `mock.indexMockFiles:
true` and auto-creates the generator if you haven't configured one.
- It's incompatible with `output.workspace` (a combined barrel is exactly the
anti-pattern this option replaces).
- `index.msw.ts` re-exports the aggregate `get<Tag>Mock` handler arrays per
tag; import a single operation's handler straight from its tag file (see the
example above).
- With `mock.generators[].schemas: true`, the consolidated faker schema
factories still live at `<schemasDir>/index.faker.ts` — the schemas barrel
never re-exports them, so importing schemas alone stays faker-free. Use
`schemasImportPath` if you want them under a dedicated import specifier.
- Set `tagsSplitDeduplication: true` alongside `artifacts` so shared inline
types are hoisted into `common-types.ts` — without it, `export *` silently
drops same-named types duplicated across per-tag files.
</Callout>

## Setting the Backend URL

Use an HTTP interceptor to automatically add the API base URL. In modern
Expand Down Expand Up @@ -343,6 +529,12 @@ import { listPetsResource } from './api/petstore.resource';
This separation works well when your app prefers signal-first reads but still
needs service methods for writes or imperative request flows.

With `output.artifacts` (see
[Runtime-safe artifact groups](#runtime-safe-artifact-groups)), both the
`*.service.ts` and sibling `*.resource.ts` files get re-exported from a single
generated client barrel, built from the files Orval actually wrote for each
tag.

### httpResource options

You can customize generated `httpResource` calls through
Expand Down
10 changes: 10 additions & 0 deletions docs/content/docs/guides/faker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,13 @@ export default defineConfig({
},
});
```

<Callout title="Tip" type="info">
If you also want a dedicated barrel for the **client** implementation (so
consumers can import it without pulling in `faker`/`msw` at all), use
`output.artifacts` to emit separate schemas/client/msw/faker groups from one
config in a single generation pass. See [Runtime-safe artifact
groups](/docs/guides/angular#runtime-safe-artifact-groups) in the Angular
guide — the option itself is client-agnostic and works the same way with
`fetch`, `axios`, and the other client packages.
</Callout>
10 changes: 10 additions & 0 deletions docs/content/docs/guides/msw.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ export { server };

If both `msw` and `faker` generators are configured with `indexMockFiles: true`, you also get an `index.faker.ts` alongside `index.msw.ts`.

<Callout title="Tip" type="info">
If you also want a dedicated barrel for the **client** implementation (so
consumers can import it without pulling in `msw`/`faker` at all), use
`output.artifacts` to emit separate schemas/client/msw/faker groups from one
config in a single generation pass. See [Runtime-safe artifact
groups](/docs/guides/angular#runtime-safe-artifact-groups) in the Angular
guide — the option itself is client-agnostic and works the same way with
`fetch`, `axios`, and the other client packages.
</Callout>

## MSW Best Practices

The generated code follows [MSW best practices](https://mswjs.io/docs/best-practices):
Expand Down
85 changes: 85 additions & 0 deletions docs/content/docs/reference/configuration/output.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,87 @@ my-app/src/
└── users.ts
```

In `tags-split` mode, [`artifacts`](#artifacts) additionally gives the client
group its own directory and generated barrel — one per-tag `*.service.ts` /
`*.resource.ts` re-export, built from the files actually written.

## artifacts

**Type:** `Object`
**Default:** `undefined`

Ergonomic sugar for emitting independently importable output groups —
schemas, client, MSW, and Faker — each with its own directory and its own
generated barrel. See the [Angular guide's runtime-safe artifact
groups](/docs/guides/angular#runtime-safe-artifact-groups) section for the
full walkthrough (including the equivalent config using only `schemas`,
`mock.generators[].path`, and `mock.indexMockFiles` directly).

```ts title="orval.config.ts"
export default defineConfig({
petstore: {
output: {
mode: 'tags-split',
target: './api/client/petstore.ts',
client: 'angular',
artifacts: {
schemas: './api/schemas',
msw: './api/msw',
faker: './api/faker',
},
},
},
});
```

| Property | Type | Description |
| --------- | ----------------------------- | ------------------------------------------------------------------------------------------------- |
| `schemas` | `string \| SchemaOptions` | Same shape as [`output.schemas`](#schemas). Conflicts with an explicit `output.schemas`. |
| `client` | `string \| { path: string }` | Client group directory. Defaults to the directory of `output.target`; if set explicitly it must equal that directory. |
| `msw` | `string \| { path: string }` | Routes the `msw` mock generator's `path` here and forces `mock.indexMockFiles: true`. |
| `faker` | `string \| { path: string }` | Routes the `faker` mock generator's `path` here and forces `mock.indexMockFiles: true`. |

Generates:

```
api/
├── schemas/
│ ├── pet.ts
│ └── index.ts
├── client/
│ ├── pets/
│ │ ├── pets.service.ts
│ │ └── pets.resource.ts
│ └── index.ts ← new: re-exports every *.service.ts/*.resource.ts actually written
├── msw/
│ ├── pets/pets.msw.ts
│ └── index.msw.ts
└── faker/
├── pets/pets.faker.ts
└── index.faker.ts
```

Interaction rules:

- Requires [`mode`](#mode) to be `'tags-split'` — every other mode inlines or
names mocks differently, so `artifacts` throws a clear error instead of
guessing.
- Requires `indexFiles` to stay enabled (its default of `true`).
- Conflicts with [`workspace`](#workspace) — a workspace barrel is a single
combined entry point, the exact anti-pattern `artifacts` replaces.
- `artifacts.schemas` conflicts with an explicit `output.schemas` — configure
the schemas group in exactly one place.
- Declaring `artifacts.msw` / `artifacts.faker` without a matching entry in
`mock.generators` auto-creates one with defaults. An explicit
`mock.generators[].path` that differs from the artifacts directory throws.
- `artifacts.client`, if set explicitly, must equal the directory of
`output.target` — per-tag client files (and, in Angular `both` mode, sibling
`*.resource.ts` files) are always written alongside `output.target`.
- Recommended: set [`tagsSplitDeduplication`](#tagssplitdeduplication) to
`true` alongside `artifacts` so shared inline types are hoisted into
`common-types.ts` — without it, the client barrel's `export *` re-exports
silently drop same-named types duplicated across per-tag files.

## baseUrl

**Type:** `String | Object`
Expand Down Expand Up @@ -598,6 +679,10 @@ export default defineConfig({
| `path` | `String` | `undefined` | Shared output directory for all mock files. Per-generator `path` values override this. When set in `single` or `tags` mode, mock code is written to separate files (relative to `path`) instead of being inlined into the implementation file. Ignored on function-form generators, which always fall back to the shared `path`. |
| `generators` | `Array<MockOptions \| Function>` | `[]` | One entry per output mock file. Each entry can be an object (`MockOptions`) or a custom `ClientMockBuilder` function. |

In `tags-split` mode, [`output.artifacts`](#artifacts) is sugar for exactly
this `path` + `indexMockFiles: true` combination — set per-group via
`artifacts.msw` / `artifacts.faker` instead of `mock.generators[].path`.

```ts title="orval.config.ts"
export default defineConfig({
petstore: {
Expand Down
Loading
Loading