@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-codegen",
3
- "version": "0.9.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`).