@dudousxd/nestjs-codegen 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +7 -3
- package/dist/cli/main.cjs +6 -2
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +6 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-SJc0gya_.d.cts → index-CxkGbILp.d.cts} +13 -3
- package/dist/{index-SJc0gya_.d.ts → index-CxkGbILp.d.ts} +13 -3
- package/dist/index.cjs +6 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +5 -1
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +5 -1
- package/dist/nest/index.js.map +1 -1
- package/package.json +6 -2
- package/skills/codegen-serialization-output/SKILL.md +148 -0
- package/skills/codegen-setup/SKILL.md +159 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/DavideCarvalho/nestjs-codegen.git",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"dist",
|
|
57
57
|
"bin",
|
|
58
58
|
"README.md",
|
|
59
|
-
"CHANGELOG.md"
|
|
59
|
+
"CHANGELOG.md",
|
|
60
|
+
"skills"
|
|
60
61
|
],
|
|
61
62
|
"dependencies": {
|
|
62
63
|
"cac": "6.7.14",
|
|
@@ -92,6 +93,9 @@
|
|
|
92
93
|
"engines": {
|
|
93
94
|
"node": ">=20"
|
|
94
95
|
},
|
|
96
|
+
"keywords": [
|
|
97
|
+
"tanstack-intent"
|
|
98
|
+
],
|
|
95
99
|
"scripts": {
|
|
96
100
|
"build": "tsup",
|
|
97
101
|
"test": "vitest run",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codegen-serialization-output
|
|
3
|
+
description: >-
|
|
4
|
+
Understand what @dudousxd/nestjs-codegen emits and how the serialization seam shapes response
|
|
5
|
+
types. Covers the generated routes.ts (ROUTES, RouteName, RouteParams, the route() helper, @As
|
|
6
|
+
name overrides), api.ts (createApi factory), and forms.ts (validation schemas), plus the
|
|
7
|
+
serialization:'json'|'superjson' config. In 'json' (default) every response type is wrapped in
|
|
8
|
+
Jsonify<...> (Date->string, bigint->never, methods dropped); 'superjson' emits the raw controller
|
|
9
|
+
return type and MUST be paired with the /superjson runtime. Use when a Date arrives as a string,
|
|
10
|
+
when choosing json vs superjson, or when wiring route() and the generated outputs.
|
|
11
|
+
metadata:
|
|
12
|
+
type: core
|
|
13
|
+
library: "@dudousxd/nestjs-codegen"
|
|
14
|
+
library_version: 0.8.0
|
|
15
|
+
framework: nestjs
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Generated output & serialization
|
|
19
|
+
|
|
20
|
+
A codegen run writes typed artifacts into `codegen.outDir`. The two that matter for every consumer
|
|
21
|
+
are `routes.ts` (typed URLs) and `api.ts` (the client factory); `forms.ts` carries validation
|
|
22
|
+
schemas. How response types are shaped depends on the `serialization` config.
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
With the module wired (see the `codegen-setup` skill), a run emits e.g. `src/generated/routes.ts`,
|
|
27
|
+
`src/generated/api.ts`, and `src/generated/forms.ts`. Consume them like this:
|
|
28
|
+
|
|
29
|
+
```ts title="src/lib/api.ts"
|
|
30
|
+
import { createApi } from '../generated/api';
|
|
31
|
+
import { createFetcher } from '@dudousxd/nestjs-client';
|
|
32
|
+
import { route } from '../generated/routes';
|
|
33
|
+
|
|
34
|
+
export const api = createApi(createFetcher({ baseUrl: '/api' }));
|
|
35
|
+
|
|
36
|
+
route('users.show', { id: '42' }); // → '/users/42'
|
|
37
|
+
route('users.list', undefined, { page: 2 }); // → '/users?page=2'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Core patterns
|
|
41
|
+
|
|
42
|
+
### 1. routes.ts is the typed URL source of truth
|
|
43
|
+
|
|
44
|
+
`routes.ts` exports a `ROUTES` map, a `RouteName` union, `RouteParams<K>`, and a runtime `route()`
|
|
45
|
+
helper that interpolates params and appends a query string:
|
|
46
|
+
|
|
47
|
+
```ts title="src/generated/routes.ts (emitted)"
|
|
48
|
+
export const ROUTES = {
|
|
49
|
+
'users.list': '/users',
|
|
50
|
+
'users.show': '/users/:id',
|
|
51
|
+
} as const;
|
|
52
|
+
export type RouteName = 'users.list' | 'users.show';
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Route names are `<controllerSegment>.<method>` (e.g. `UsersController#list` → `users.list`). Override
|
|
56
|
+
either segment with `@As('…')` at the class or method level. A wrong/unknown route name is a type
|
|
57
|
+
error; a missing required param throws at runtime.
|
|
58
|
+
Source: `apps/docs/content/docs/client/routes.mdx`, `packages/core/src/emit/emit-routes.ts`,
|
|
59
|
+
`packages/core/src/discovery/contracts-fast.ts` (`getDecorator('As')`).
|
|
60
|
+
|
|
61
|
+
### 2. api.ts is a factory you inject the fetcher into
|
|
62
|
+
|
|
63
|
+
`api.ts` exports `createApi(fetcher)` and an `Api` type — never a hardcoded transport. You build the
|
|
64
|
+
client once with your fetcher; each leaf is an awaitable, fully-typed handle:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const users = await api.users.list(); // typed response
|
|
68
|
+
const user = await api.users.show({ params: { id } });
|
|
69
|
+
const made = await api.users.create({ body }); // body typed from the DTO
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Source: `apps/docs/content/docs/client/api-client.mdx`, `packages/core/src/emit/emit-api.ts`.
|
|
73
|
+
|
|
74
|
+
### 3. serialization decides the response type shape
|
|
75
|
+
|
|
76
|
+
`serialization` (config key, default `'json'`) controls whether the emitted `response` type models
|
|
77
|
+
the JSON wire shape or the raw server type:
|
|
78
|
+
|
|
79
|
+
```ts title="nestjs-codegen.config.ts"
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
// ...
|
|
82
|
+
serialization: 'json', // default: wrap responses in Jsonify<...>
|
|
83
|
+
// serialization: 'superjson', // emit raw controller return types (revived at runtime)
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`'json'` wraps every `response` in the type-only `Jsonify<...>` from `@dudousxd/nestjs-client`:
|
|
88
|
+
`Date` (and anything with `toJSON()`) → its serialized shape, arrays/objects recurse, non-serializable
|
|
89
|
+
props are dropped, `bigint` → `never`. It's a compile-time transform with no runtime cost. `body`,
|
|
90
|
+
`query`, `params`, and `error` are emitted as-is.
|
|
91
|
+
Source: `packages/core/src/config/types.ts` (`serialization`), `apps/docs/content/docs/client/api-client.mdx`.
|
|
92
|
+
|
|
93
|
+
## Common mistakes
|
|
94
|
+
|
|
95
|
+
### Treating a json-mode response as a runtime Date
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// ❌ Wrong — controller returns { createdAt: Date }, but under serialization:'json' the wire is JSON
|
|
99
|
+
const u = await api.users.show({ params: { id } });
|
|
100
|
+
u.createdAt.getFullYear(); // type error: createdAt is `string` (Jsonify maps Date -> string)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// ✅ Correct — it's the ISO string the server actually sent
|
|
105
|
+
const u = await api.users.show({ params: { id } });
|
|
106
|
+
new Date(u.createdAt).getFullYear();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`JSON.stringify(new Date())` produces an ISO string, so `Jsonify` types `createdAt` as `string` to
|
|
110
|
+
stop the type from lying about the wire.
|
|
111
|
+
Source: `apps/docs/content/docs/client/api-client.mdx` ("Response types & serialization").
|
|
112
|
+
|
|
113
|
+
### Flipping serialization:'superjson' without the runtime
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// ❌ Wrong — config emits raw Date types, but the client still parses plain JSON → values are strings
|
|
117
|
+
defineConfig({ serialization: 'superjson' });
|
|
118
|
+
// (no /superjson fetcher options, no SuperjsonInterceptor on the server)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// ✅ Correct — pair the config with the runtime that revives the types (see nestjs-client-runtime)
|
|
123
|
+
import { superjsonFetcherOptions } from '@dudousxd/nestjs-client/superjson';
|
|
124
|
+
createApi(createFetcher({ baseUrl: '/api', ...superjsonFetcherOptions() }));
|
|
125
|
+
// + register SuperjsonInterceptor on the server
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`serialization:'superjson'` only turns OFF compile-time `Jsonify` wrapping; the actual revival of
|
|
129
|
+
`Date`/`Map`/`Set`/`BigInt` happens at runtime via the `/superjson` subpath and the server
|
|
130
|
+
interceptor. Without both, the types claim `Date` but the value is still a string.
|
|
131
|
+
Source: `apps/docs/content/docs/client/fetcher.mdx` ("superjson runtime"), `packages/client/src/superjson/index.ts`.
|
|
132
|
+
|
|
133
|
+
### Not gating generated drift in CI
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# ❌ Wrong — generate but never check, so a stale committed client silently ships
|
|
137
|
+
npx nestjs-codegen codegen
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# ✅ Correct — fail the build when the committed artifacts are stale
|
|
142
|
+
npx nestjs-codegen codegen
|
|
143
|
+
git diff --exit-code src/generated
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The dev watcher is skipped in production, so the artifacts you ship are whatever is committed —
|
|
147
|
+
gate them or they drift from your routes.
|
|
148
|
+
Source: `apps/docs/content/docs/getting-started.mdx` ("Generate in CI").
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codegen-setup
|
|
3
|
+
description: >-
|
|
4
|
+
Set up @dudousxd/nestjs-codegen in a NestJS app. Wire NestjsCodegenModule.forRoot from
|
|
5
|
+
@dudousxd/nestjs-codegen/nest with contracts.glob + codegen.outDir + a validation ADAPTER
|
|
6
|
+
INSTANCE (zodAdapter from @dudousxd/nestjs-codegen-zod, or valibotAdapter/arktypeAdapter).
|
|
7
|
+
Author one defineConfig nestjs-codegen.config.ts as the single source of truth and import it into
|
|
8
|
+
forRoot(); run the nestjs-codegen codegen / init / doctor CLI as a CI drift gate. Covers the
|
|
9
|
+
boot-time watcher (skipped when NODE_ENV=production), the enabled/cwd module fields, and why a
|
|
10
|
+
bare validation:'zod' string throws ConfigError. Use for install, wiring, config, CI generation.
|
|
11
|
+
metadata:
|
|
12
|
+
type: core
|
|
13
|
+
library: "@dudousxd/nestjs-codegen"
|
|
14
|
+
library_version: 0.8.0
|
|
15
|
+
framework: nestjs
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# nestjs-codegen setup
|
|
19
|
+
|
|
20
|
+
`@dudousxd/nestjs-codegen` discovers your NestJS controllers/contracts/DTOs and emits a typed
|
|
21
|
+
client (`routes.ts`, `api.ts`, `forms.ts`) into an output dir. In dev the `NestjsCodegenModule`
|
|
22
|
+
runs a watcher with your app; in CI the `nestjs-codegen` CLI does a one-shot, watch-free run.
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
Install the codegen, the runtime the generated client imports from, and a validation adapter:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add -D @dudousxd/nestjs-codegen @dudousxd/nestjs-codegen-zod
|
|
30
|
+
pnpm add @dudousxd/nestjs-client
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Author one config file with `defineConfig` — this is the single source of truth the CLI loads and
|
|
34
|
+
the module reuses:
|
|
35
|
+
|
|
36
|
+
```ts title="nestjs-codegen.config.ts"
|
|
37
|
+
import { defineConfig } from '@dudousxd/nestjs-codegen';
|
|
38
|
+
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
contracts: { glob: 'src/**/*.controller.ts' }, // controllers to scan for routes + contracts
|
|
42
|
+
codegen: { outDir: 'src/generated' }, // where routes.ts / api.ts / forms.ts land
|
|
43
|
+
validation: zodAdapter, // an ADAPTER INSTANCE, not the string 'zod'
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Wire the module into your root module and import that config so dev + CI never drift:
|
|
48
|
+
|
|
49
|
+
```ts title="src/app.module.ts"
|
|
50
|
+
import { Module } from '@nestjs/common';
|
|
51
|
+
import { NestjsCodegenModule } from '@dudousxd/nestjs-codegen/nest';
|
|
52
|
+
import codegenConfig from '../nestjs-codegen.config';
|
|
53
|
+
|
|
54
|
+
@Module({
|
|
55
|
+
imports: [NestjsCodegenModule.forRoot(codegenConfig)],
|
|
56
|
+
})
|
|
57
|
+
export class AppModule {}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Run the app as usual (`nest start --watch`): the watcher does an initial generate, then regenerates
|
|
61
|
+
as controllers/DTOs change. `@nestjs/common` is an optional peer — your Nest app already has it.
|
|
62
|
+
|
|
63
|
+
## Core patterns
|
|
64
|
+
|
|
65
|
+
### 1. forRoot() options ARE the config (plus two module-only fields)
|
|
66
|
+
|
|
67
|
+
`CodegenModuleOptions` is `UserConfig` plus `enabled` and `cwd`. You can inline the options instead
|
|
68
|
+
of importing a file, but a shared `defineConfig` file keeps the CLI in sync:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
NestjsCodegenModule.forRoot({
|
|
72
|
+
contracts: { glob: 'src/**/*.controller.ts' },
|
|
73
|
+
codegen: { outDir: 'src/generated' },
|
|
74
|
+
validation: zodAdapter,
|
|
75
|
+
enabled: true, // module-only: force the watcher on (see pattern 3)
|
|
76
|
+
cwd: process.cwd(), // module-only: project root for glob/outDir resolution
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Source: `packages/core/src/nest/module.ts` (`CodegenModuleOptions`), `packages/core/src/config/types.ts`.
|
|
81
|
+
|
|
82
|
+
### 2. The CLI is your CI drift gate
|
|
83
|
+
|
|
84
|
+
The `nestjs-codegen` bin reads `nestjs-codegen.config.ts`. In CI, regenerate and fail on any diff so
|
|
85
|
+
a stale committed client can never ship:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx nestjs-codegen codegen # one-shot generate
|
|
89
|
+
npx nestjs-codegen codegen --watch # same watcher, standalone
|
|
90
|
+
npx nestjs-codegen init # scaffold a starter config
|
|
91
|
+
npx nestjs-codegen doctor # diagnose missing config / unscanned controllers / drift
|
|
92
|
+
git diff --exit-code src/generated # non-zero (fails CI) if the client is stale
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Source: `apps/docs/content/docs/cli.mdx`, `packages/core/bin/nestjs-codegen.js`.
|
|
96
|
+
|
|
97
|
+
### 3. The watcher is a dev/CI concern — off in production by default
|
|
98
|
+
|
|
99
|
+
`shouldRun` returns `false` when `process.env.NODE_ENV === 'production'` unless you set `enabled`
|
|
100
|
+
explicitly. Codegen is a build step, not a runtime dependency, so leave it default and let prod skip
|
|
101
|
+
it; only set `enabled: false` to turn it off everywhere or `enabled: true` to force it.
|
|
102
|
+
|
|
103
|
+
Source: `packages/core/src/nest/module.ts` (`shouldRun`).
|
|
104
|
+
|
|
105
|
+
## Common mistakes
|
|
106
|
+
|
|
107
|
+
### Passing the validation library as a string
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// ❌ Wrong — throws ConfigError at config-resolve time
|
|
111
|
+
defineConfig({ validation: 'zod' });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// ✅ Correct — import and pass the adapter instance
|
|
116
|
+
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';
|
|
117
|
+
defineConfig({ validation: zodAdapter });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
No adapter is bundled in core; `resolveAdapter` throws a `ConfigError` for any string and tells you
|
|
121
|
+
to install the package and pass the instance. (Some older docs show `validation: 'zod'` — the source
|
|
122
|
+
is authoritative.) For valibot/arktype, import `valibotAdapter` / `arktypeAdapter` from their
|
|
123
|
+
packages.
|
|
124
|
+
Source: `packages/core/src/adapters/registry.ts` (`resolveAdapter`).
|
|
125
|
+
|
|
126
|
+
### Maintaining a second, divergent config for the CLI
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// ❌ Wrong — forRoot() inlines one set of options, the CLI reads a different file → drift
|
|
130
|
+
NestjsCodegenModule.forRoot({ contracts: { glob: 'src/**/*.controller.ts' }, codegen: { outDir: 'gen' }, validation: zodAdapter });
|
|
131
|
+
// nestjs-codegen.config.ts says outDir: 'src/generated' → CI generates to a different place
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// ✅ Correct — one defineConfig file, imported into forRoot()
|
|
136
|
+
import codegenConfig from '../nestjs-codegen.config';
|
|
137
|
+
NestjsCodegenModule.forRoot(codegenConfig);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The module (dev) and the CLI (CI) must resolve the same options or the CI drift check compares
|
|
141
|
+
against artifacts the dev watcher never wrote.
|
|
142
|
+
Source: `apps/docs/content/docs/getting-started.mdx` ("single source of truth").
|
|
143
|
+
|
|
144
|
+
### Expecting the watcher to run in your production container
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// ❌ Wrong — assuming forRoot() regenerates the client in prod
|
|
148
|
+
NestjsCodegenModule.forRoot(codegenConfig); // does nothing when NODE_ENV=production
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// ✅ Correct — generate in CI before deploy; commit the artifacts
|
|
153
|
+
// CI step: npx nestjs-codegen codegen && git diff --exit-code src/generated
|
|
154
|
+
NestjsCodegenModule.forRoot(codegenConfig); // dev-only watcher; prod ships committed files
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The boot-time watcher is intentionally skipped in production; the generated files are meant to be
|
|
158
|
+
generated in CI and committed, not produced at runtime.
|
|
159
|
+
Source: `packages/core/src/nest/module.ts` (`onApplicationBootstrap` guards on `shouldRun`).
|