@tommasomeli/prisma-generator-nestjs-dto 0.1.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2026-05-21
6
+
7
+ First release.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tommasomeli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,552 @@
1
+ <div align="center">
2
+
3
+ # Prisma Generator NestJS DTO
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@tommasomeli/prisma-generator-nestjs-dto.svg)](https://www.npmjs.com/package/@tommasomeli/prisma-generator-nestjs-dto)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
+ [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-FFDD00?logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/tommasomeli)
8
+
9
+ A Prisma generator for NestJS DTOs with first-class **plugin** and **annotation** APIs.<br/>
10
+ Emits `Create*Dto` / `Update*Dto` / `*Entity` classes with `class-validator` + `@nestjs/swagger` decorators, then gets out of your way.
11
+
12
+ </div>
13
+
14
+ ## Features
15
+
16
+ - **Pluggable sub-generators** — drop in any `BaseGenerator` subclass next to the built-in DTOs (`extraGenerators`). TypeScript plugin files are loaded directly via `jiti`, no precompilation needed.
17
+ - **Lifecycle hooks** — optional `beforeAll(models)` / `afterAll(files)` on every generator for shared pre/post-passes (model mutation, aggregated barrels, audit reports).
18
+ - **Custom annotations** — register `@MyAnnotation(args)` names and read them inside your plugins (`extraAnnotations`).
19
+ - **Per-name override of built-in imports** — swap `@IsBoolean` for a permissive variant without touching the DTOs.
20
+ - **Scalar overrides** (`extraScalars`) — reroute Prisma scalars (`Decimal`, `Json`, ...) to your own TS types + import + Swagger metadata.
21
+ - **Type-safe external config** — `.ts` / `.json` config with `from()` / `fromNamespace()` helpers that validate paths and named exports at compile time.
22
+ - **Annotation-driven decorators / validators / imports** — bind any `///` annotation to a user module via `extraDecorators` / `extraValidators` / `extraImports`.
23
+ - **Optional runtime manifest** (`emitManifest`) for select builders, audit middleware, RBAC field lists.
24
+ - **Auto-imports** for `@DtoOverrideType`, relation DTOs and annotation arguments — emitted files are always self-contained.
25
+
26
+ ## Contents
27
+
28
+ - [Install](#install)
29
+ - [Quickstart](#quickstart)
30
+ - [Configuration](#configuration)
31
+ - [Plugin system - custom generator](#plugin-system---custom-generator)
32
+ - [Extra features](#extra-features)
33
+ - [External config file](#external-config-file)
34
+ - [Annotations](#annotations)
35
+ - [Manifest output (opt-in)](#manifest-output-opt-in)
36
+ - [Comparison](#comparison)
37
+ - [Contributing](#contributing)
38
+ - [How it works](#how-it-works)
39
+ - [License](#license)
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm i -D @tommasomeli/prisma-generator-nestjs-dto
45
+ ```
46
+
47
+ Peers: `@prisma/generator-helper >= 5`, `prettier >= 3` (optional).
48
+
49
+ ## Quickstart
50
+
51
+ ```prisma
52
+ generator nestjsDto {
53
+ provider = "prisma-generator-nestjs-dto"
54
+ output = "../src/generated/nestjs-dto"
55
+ outputType = "class"
56
+ outputStructure = "nestjs"
57
+ fileNamingStrategy = "kebab"
58
+ reExport = "true"
59
+ classValidator = "true"
60
+ swaggerDocs = "true"
61
+ prettier = "true"
62
+ }
63
+ ```
64
+
65
+ ```bash
66
+ npx prisma generate
67
+ ```
68
+
69
+ For every model `User` you get:
70
+
71
+ ```
72
+ src/generated/nestjs-dto/
73
+ user/
74
+ user.entity.ts
75
+ create-user.dto.ts
76
+ update-user.dto.ts
77
+ index.ts
78
+ index.ts
79
+ ```
80
+
81
+ ## Configuration
82
+
83
+
84
+ | Option | Type | Default | Description |
85
+ | -------------------- | --------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | `output` | string | `../src/generated/nestjs-dto` | Destination directory. |
87
+ | `configFile` | string | — | Path to an external `.ts` / `.cts` / `.mts` / `.js` / `.cjs` / `.mjs` / `.json` config file. See [External config file](#external-config-file). |
88
+ | `outputType` | `class` | `interface` | `class` | TypeScript classes (with decorators) or plain interfaces. |
89
+ | `outputStructure` | `nestjs` | `flat` | `nestjs` | `nestjs` puts each model in its own folder, `flat` keeps everything in `output`. |
90
+ | `fileNamingStrategy` | `camel` | `snake` | `kebab` | `camel` | File name casing. |
91
+ | `reExport` | `"true"` | `"false"` | `"true"` | Emit `index.ts` barrels. |
92
+ | `classValidator` | `"true"` | `"false"` | `"true"` | Emit `class-validator` decorators. |
93
+ | `swaggerDocs` | `"true"` | `"false"` | `"true"` | Emit `@nestjs/swagger` `@ApiProperty` decorators. |
94
+ | `prettier` | `"true"` | `"false"` | `"false"` | Format the output with Prettier (auto-resolves config). |
95
+ | `schemaDir` | string | — | Directory of additional `.prisma` files to scan for `@ignore`d fields. |
96
+ | `emitManifest` | `"true"` | `"false"` | `"false"` | Emit `manifest.ts` and `model-entity-map.ts` alongside the DTOs. |
97
+ | `extraGenerators` | string | string[] | — | Additional sub-generators. See [Plugin system](#plugin-system---custom-generator). |
98
+ | `extraAnnotations` | string | string[] | — | Names of user-defined annotations consumed by custom sub-generators. |
99
+ | `extraDecorators` | string | string[] | — | Field-level decorators triggered by matching `@Annotation` names. See [Extra features](#extra-features). |
100
+ | `extraValidators` | string | string[] | — | Same as `extraDecorators`, also drives `class-validator` override-by-name. |
101
+ | `extraImports` | string | string[] | — | Imports referenced **inside annotation parameters** (e.g. `@ApiProperty({ example: USER_EXAMPLE })`). |
102
+ | `extraScalars` | object (configFile only) | — | Per-scalar overrides for TS type, Swagger `type`/`format`, and the imported module. See [Extra features](#extra-features). |
103
+
104
+
105
+ > `extra*` options accept simple strings in `schema.prisma`. Anything more elaborate (arrays, descriptors, mixed forms) belongs in a [`configFile`](#external-config-file) — Prisma's generator block does not support multi-line arrays or nested objects.
106
+
107
+ ## Plugin system - custom generator
108
+
109
+ A plugin is a class extending `BaseGenerator`. The base class hands you the parsed model graph, the user config, and the import-merging machinery used by the built-in generators — so a plugin stays short, plugs into the same pipeline, and emits any TypeScript file. Register the annotations it recognises via `extraAnnotations` (the parser is always on; this is the discovery list `this.config.extraAnnotations` exposes).
110
+
111
+ ### Basic example — transform a `Model` and reuse the built-in renderer
112
+
113
+ The shortest plugin filters/transforms a `Model` and lets `getTemplate(...)` do the rest. This is the built-in **`EntityDtoGenerator`** verbatim:
114
+
115
+ ```ts
116
+ import { isEntityHidden } from '../annotations';
117
+ import { BaseGenerator } from '../base-generator';
118
+ import { Field, File, Model } from '../types';
119
+
120
+ export default class EntityDtoGenerator extends BaseGenerator {
121
+ filePrefix = '';
122
+ fileSuffix = '.entity';
123
+ classPrefix = '';
124
+ classSuffix = '';
125
+
126
+ async generate(): Promise<File[]> {
127
+ return this.models.map((model) => {
128
+ const filteredFields = model.fields.filter((field: Field) => !isEntityHidden(field));
129
+ const processedModel: Model = { ...model, fields: filteredFields as Field[] };
130
+ const outputPath = this.getPath(model);
131
+ return {
132
+ path: outputPath,
133
+ content: this.getTemplate({ model: processedModel, classValidator: false, outputPath })
134
+ };
135
+ });
136
+ }
137
+ }
138
+ ```
139
+
140
+ `isEntityHidden` respects `@DtoHidden` / `@DtoEntityHidden` / `@DtoApiHidden`. `getTemplate({ classValidator: false })` opts the entity out of `class-validator` decorators while keeping Swagger ones.
141
+
142
+ ### Advanced example: `GqlDtoGenerator`
143
+
144
+ Opt-in GraphQL plugin: emits a `@nestjs/graphql` `@ObjectType` for every model annotated with `@GqlObjectType`, reacts to `@GqlField` / `@GqlHidden`, honours the built-in `@DtoIgnoreModel` / `@DtoHidden` / `@DtoEntityHidden`, and reuses `addImport` / `formatImports` for import management:
145
+
146
+ ```ts
147
+ // my-generators/gql-generator.ts
148
+ import {
149
+ BaseGenerator,
150
+ DTO_ENTITY_HIDDEN,
151
+ DTO_HIDDEN,
152
+ DTO_IGNORE_MODEL,
153
+ Field,
154
+ File,
155
+ ImportType,
156
+ Model
157
+ } from '@tommasomeli/prisma-generator-nestjs-dto';
158
+
159
+ const PRISMA_TO_GQL_SCALAR: Record<string, string> = {
160
+ String: 'String', Boolean: 'Boolean', Int: 'Int', BigInt: 'Int',
161
+ Float: 'Float', Decimal: 'Float', DateTime: 'Date', Json: 'GraphQLJSON'
162
+ };
163
+ const PRIMITIVE_GQL_TYPES = new Set(['ID', 'Int', 'Float']);
164
+
165
+ export class GqlDtoGenerator extends BaseGenerator {
166
+ filePrefix = '';
167
+ fileSuffix = '.gql';
168
+ classPrefix = '';
169
+ classSuffix = '';
170
+
171
+ async generate(): Promise<File[]> {
172
+ return this.models
173
+ .filter((model) => this.hasAnnotation(model, 'GqlObjectType') && !this.hasAnnotation(model, DTO_IGNORE_MODEL))
174
+ .map((model) => this.renderModel(model));
175
+ }
176
+
177
+ private renderModel(model: Model): File {
178
+ const typeName = String(this.getAnnotation(model, 'GqlObjectType')?.params?.[0] ?? model.name);
179
+ const visibleFields = model.fields.filter(
180
+ (f) => !this.hasAnnotation(f, 'GqlHidden') && !this.hasAnnotation(f, DTO_HIDDEN) && !this.hasAnnotation(f, DTO_ENTITY_HIDDEN)
181
+ );
182
+ const outputPath = this.getPath(model);
183
+
184
+ const imports: ImportType[] = [];
185
+ const gqlSymbols = new Set<string>(['ObjectType', 'Field']);
186
+
187
+ const fieldLines = visibleFields.map((f) => {
188
+ const gqlType = this.gqlTypeOf(f);
189
+ if (PRIMITIVE_GQL_TYPES.has(gqlType)) gqlSymbols.add(gqlType);
190
+
191
+ if (f.kind === 'object') {
192
+ const related = this.models.find((m) => m.name === f.type);
193
+ if (related && this.hasAnnotation(related, 'GqlObjectType')) {
194
+ this.addImport(imports, { from: this.getImportPath(related, outputPath), destruct: [f.type] });
195
+ }
196
+ } else if (f.kind === 'enum') {
197
+ this.addImport(imports, { from: '@prisma/client', destruct: [f.type] });
198
+ }
199
+
200
+ return this.renderField(f, gqlType);
201
+ });
202
+
203
+ this.addImport(imports, { from: '@nestjs/graphql', destruct: [...gqlSymbols] });
204
+
205
+ return {
206
+ path: outputPath,
207
+ content: `${this.formatImports(imports, outputPath)}
208
+
209
+ @ObjectType('${typeName}')
210
+ export class ${typeName} {
211
+ ${fieldLines.join('\n\n')}
212
+ }
213
+ `
214
+ };
215
+ }
216
+
217
+ private renderField(f: Field, gqlType: string): string {
218
+ const ref = f.isList ? `[${gqlType}]` : gqlType;
219
+ const opts = !f.isRequired ? ', { nullable: true }' : '';
220
+ const tsBase = f.kind === 'object' || f.kind === 'enum' ? f.type : this.tsScalar(f.type);
221
+ const tsType = `${tsBase}${f.isList ? '[]' : ''}${f.isRequired ? '' : ' | null'}`;
222
+ return ` @Field(() => ${ref}${opts})\n ${f.name}${f.isRequired ? '!' : '?'}: ${tsType};`;
223
+ }
224
+
225
+ private gqlTypeOf(f: Field): string {
226
+ const forced = this.getAnnotation(f, 'GqlField')?.params?.[0] as string | undefined;
227
+ if (forced) return forced;
228
+ if (f.isId && (f.type === 'Int' || f.type === 'String')) return 'ID';
229
+ if (f.kind === 'enum' || f.kind === 'object') return f.type;
230
+ return PRISMA_TO_GQL_SCALAR[f.type] ?? f.type;
231
+ }
232
+
233
+ private tsScalar(t: string): string {
234
+ return t === 'Boolean' ? 'boolean' : t === 'Int' || t === 'Float' ? 'number' : t === 'DateTime' ? 'Date' : t === 'Json' ? 'unknown' : 'string';
235
+ }
236
+ }
237
+ ```
238
+
239
+ Annotate the Prisma schema and wire the plugin from the `configFile`:
240
+
241
+ ```prisma
242
+ /// @GqlObjectType("User")
243
+ model User {
244
+ /// @GqlField(ID)
245
+ id Int @id @default(autoincrement())
246
+ email String @unique
247
+ name String
248
+ /// @GqlHidden
249
+ passwordHash String
250
+ posts Post[]
251
+ }
252
+
253
+ /// @GqlObjectType("Post")
254
+ model Post {
255
+ id Int @id @default(autoincrement())
256
+ title String
257
+ author User @relation(fields: [authorId], references: [id])
258
+ authorId Int
259
+ }
260
+ ```
261
+
262
+ ```ts
263
+ // nestjs-dto.config.ts
264
+ export default {
265
+ extraAnnotations: ['GqlObjectType', 'GqlField', 'GqlHidden'],
266
+ extraGenerators: from('./dist/my-generators/gql-generator.cjs', ['GqlDtoGenerator'])
267
+ } satisfies GeneratorConfigFile;
268
+ ```
269
+
270
+ Plugins are loaded with `require()` from the cwd — point at a **compiled JS/CJS** file. A plugin re-exporting a built-in name (`CreateDtoGenerator`, `EntityGenerator`, `UpdateDtoGenerator`) **replaces** it in the registry.
271
+
272
+ `npx prisma generate` emits one file per opted-in model:
273
+
274
+ ```ts
275
+ // src/generated/nestjs-dto/user/user.gql.ts
276
+ import { Post } from '../post/post.gql';
277
+ import { ObjectType, Field, ID } from '@nestjs/graphql';
278
+
279
+ @ObjectType('User')
280
+ export class User {
281
+ @Field(() => ID)
282
+ id!: number;
283
+ @Field(() => String)
284
+ email!: string;
285
+ @Field(() => String)
286
+ name!: string;
287
+ @Field(() => [Post])
288
+ posts!: Post[];
289
+ }
290
+ ```
291
+
292
+ `passwordHash` is dropped (`@GqlHidden`), `id` becomes `ID` (`@GqlField(ID)`), and the `Post` import is resolved relative to the emitted file by `formatImports`. Drop `@GqlObjectType` on `Post` and the `posts` field stays but the import is omitted (guard in `renderModel`).
293
+
294
+ ### Types used by plugins
295
+
296
+ All plugin-facing types are re-exported from the package root; full shapes live in `dist/index.d.ts`. The fields you'll touch most:
297
+
298
+ - **`Annotation`** — `{ name, params }`. `name` is the bare identifier (no `@`); `params` is pre-parsed (numbers stay numbers, identifiers stay strings, `{ ... }` literals stay raw).
299
+ - **`Field`** — `name`, `type`, `kind` (`'scalar' | 'object' | 'enum' | 'unsupported'`), `isList`, `isRequired`, `isId`, `isUnique`, `annotations`, plus the usual Prisma relation metadata.
300
+ - **`Model`** — `name`, `fields`, `annotations`, `primaryKey`, `uniqueFields`.
301
+ - **`File`** — `{ path, content }`, what your `generate()` returns.
302
+ - **`ImportType`** — `{ from, destruct?, alias? }`, what `addImport` / `formatImports` consume.
303
+
304
+ `this.config.extraValidators` / `extraDecorators` / `extraImports` are pre-parsed into `ImportType[]`; `this.config.extraAnnotations` is the registered `string[]`.
305
+
306
+ ### `BaseGenerator` API at a glance
307
+
308
+ | Member | Kind | Purpose |
309
+ | -------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
310
+ | `this.models: Model[]` | property | All non-`@DtoIgnoreModel` models, with parsed `annotations` on each `Field` / `Model`. |
311
+ | `this.config: GeneratorConfig` | property | Resolved config: paths, output options, parsed `extraDecorators` / `extraValidators` / `extraImports` / `extraAnnotations`. |
312
+ | `this.options: GeneratorOptions` | property | Raw Prisma `GeneratorOptions` (incl. DMMF) — for advanced use cases. |
313
+ | `filePrefix` / `fileSuffix` / `classPrefix` / `classSuffix` | abstract | Naming hooks every subclass must declare. |
314
+ | `generate(): Promise<File[]>` | abstract | Your plugin's body. Return one or more `{ path, content }` entries. |
315
+ | `beforeAll(models): Promise<void>` | method | Optional pre-pass invoked once before any `generate()`. Receives the **shared** `Model[]`; mutating it is visible to every later hook and `generate()` call. Defaults to no-op. |
316
+ | `afterAll(files): Promise<File[] \| void>` | method | Optional post-pass invoked once after every `generate()`. Receives the full list of files produced in the run; return a non-empty `File[]` to append more files (barrels, audit reports, schema exports). |
317
+ | `getAnnotation(target, name)` | method | Returns `Annotation \| undefined` (with `.params`) for any field/model. |
318
+ | `hasAnnotation(target, name)` | method | Boolean form of the above. |
319
+ | `getPath(model, relativeFrom?)` | method | Output path for a model on disk (with `.ts`). Pass the current file's path as `relativeFrom` to get a relative path. |
320
+ | `getImportPath(model, fromOutputPath?)` | method | Same as `getPath` but stripped of the `.ts` extension — use it when building an `ImportType.from` that references a peer-generated file. |
321
+ | `getModelName(name)` | method | Applies the configured naming strategy (`camel` / `snake` / `kebab`) to a model name. |
322
+ | `addImport(imports, next)` | method | Merges an `ImportType` into an array (dedup + alias/destruct separation, same rules as the built-in pipeline). |
323
+ | `formatImports(imports, outputPath?)` | method | Renders an `ImportType[]` as a multi-line `import ... from '...'` block. Rewrites absolute paths relative to `outputPath` and strips `.ts` / `.mts` / `.cts` / `.tsx`. |
324
+ | `getTemplate({ model, classValidator?, swaggerDocs?, outputPath? })` | method | Renders the full class body (imports + decorators + fields) you'd get from a built-in generator. Useful as escape hatch when your plugin only needs to **transform** a model. |
325
+
326
+ > **Naming gotcha.** Built-in barrel emission and the `re-export` machinery assume your plugin's `fileSuffix` ends with a single segment matching the file's role (`.entity`, `.dto`, `.gql`, ...). If you pick an unusual or empty suffix and use `reExport: "true"`, you may see duplicate `export * from './foo';` lines in the per-model `index.ts` when two generators land on the same path. Choose distinct suffixes (`.audit`, `.cqrs.command`, ...) or set `reExport: "false"` and emit your own barrel from `afterAll` to avoid the collision.
327
+
328
+ ## Extra features
329
+
330
+ The `extra*` config fields share the same [declaration syntax](#declaration-syntax); they differ in **how the generator wires them in**.
331
+
332
+ ### `extraValidators` / `extraDecorators` — annotation-driven decorators
333
+
334
+ Bind any `@Annotation` placed on a `///` comment to a symbol imported from a user module. Same lookup, different intent: `extraValidators` for `class-validator` rules, `extraDecorators` for everything else (Swagger, `class-transformer`, custom NestJS metadata, ...). Pick the one that matches the override module (see [Override of built-in imports](#override-of-built-in-imports)).
335
+
336
+ ```ts
337
+ extraValidators: from(() => import('src/common/validators'), ['IsUnique', 'IsStrongPassword']),
338
+ extraDecorators: from(() => import('src/common/decorators'), ['Trim', 'Sanitize'])
339
+ ```
340
+
341
+ ```prisma
342
+ model User {
343
+ /// @IsUnique()
344
+ email String @unique
345
+ /// @IsStrongPassword({ minLength: 10 })
346
+ password String
347
+ /// @Trim()
348
+ /// @Sanitize('xss')
349
+ name String
350
+ }
351
+ ```
352
+
353
+ If a local name **collides with a built-in symbol** (`IsBoolean`, `ApiProperty`, ...), the user module wins — see below.
354
+
355
+ ### `extraImports` — symbols referenced inside annotation parameters
356
+
357
+ While `extraDecorators` / `extraValidators` resolve annotation **names**, `extraImports` resolves identifiers used **inside** annotation parameters (and inside `@DtoApiExtraModels(...)` at the model level). Declare the source module, the generator imports the symbol in every file that references it.
358
+
359
+ ```prisma
360
+ /// @ApiProperty({ example: USER_EXAMPLE })
361
+ /// @Matches(USERNAME_REGEX)
362
+ name String
363
+ ```
364
+
365
+ ```ts
366
+ extraImports: [
367
+ from(() => import('src/users/user.fixtures'), 'USER_EXAMPLE'),
368
+ from(() => import('src/users/user.regex'), 'USERNAME_REGEX')
369
+ ]
370
+ ```
371
+
372
+ ### `extraGenerators` — pluggable sub-generators
373
+
374
+ `BaseGenerator` subclasses that run alongside the built-ins. See [Plugin system](#plugin-system---custom-generator) for the API and an end-to-end example. Re-exporting a built-in name (`CreateDtoGenerator`, `UpdateDtoGenerator`, `EntityGenerator`) **replaces** it. `.ts` / `.cts` / `.mts` / `.tsx` paths are loaded via [`jiti`](https://github.com/unjs/jiti) (no precompilation); everything else goes through native `require`.
375
+
376
+ ```ts
377
+ extraGenerators: from('./generators/audit-generator.ts', ['AuditGenerator'])
378
+ ```
379
+
380
+ ### `extraScalars` — per-scalar overrides
381
+
382
+ Reroute a Prisma scalar to your own TS type, with optional Swagger metadata and an automatically-emitted import. Only available from a [`configFile`](#external-config-file) (the schema block cannot express nested objects).
383
+
384
+ ```ts
385
+ extraScalars: {
386
+ Decimal: { ts: 'Decimal', from: 'decimal.js' },
387
+ Json: { ts: 'MyJson', from: 'src/json', apiType: 'Object' },
388
+ BigInt: { ts: 'bigint' } // no import needed for ambient types
389
+ }
390
+ ```
391
+
392
+ - `ts` — TypeScript type used in field declarations.
393
+ - `from` *(optional)* — module to import `ts` from. When set, every DTO that references the scalar gets the import.
394
+ - `apiType` *(optional)* — value used in `@ApiProperty({ type: ... })`. Defaults to `() => ts` when `from` is set, `'<ts>'` otherwise.
395
+ - `format` *(optional)* — value used in `@ApiProperty({ format })`.
396
+
397
+ The built-in `Prisma.Json` / `Prisma.Decimal` import from `@prisma/client` is skipped automatically for any scalar the user has overridden.
398
+
399
+ ### `extraAnnotations` — custom annotation discovery
400
+
401
+ The parser is always on: any `@Name(args)` in a `///` comment ends up as `{ name, params }` on the `Field` / `Model`. `extraAnnotations` is the **discovery list** your plugins read from `this.config.extraAnnotations` — for warnings, iteration, feature toggles.
402
+
403
+ ```ts
404
+ extraAnnotations: ['DtoAuditable', 'DtoIndexed']
405
+
406
+ // inside a plugin
407
+ const indexed = this.getAnnotation(field, 'DtoIndexed');
408
+ ```
409
+
410
+ ### Override of built-in imports
411
+
412
+ A descriptor exporting a name that collides with a symbol the generator would normally pull from a built-in module **wins** for that name only; unrelated symbols still come from the default.
413
+
414
+ | `extra*` field | Default modules it can override |
415
+ | ----------------- | ----------------------------------------------------------------------------- |
416
+ | `extraValidators` | `class-validator` (`IsBoolean`, `IsString`, `IsInt`, `IsOptional`, ...) |
417
+ | `extraDecorators` | `@nestjs/swagger` (`ApiProperty`, `ApiExtraModels`, ...), `class-transformer` |
418
+ | `extraImports` | `@prisma/client` (`Prisma`, enum types), generated relation DTOs |
419
+ | `extraGenerators` | Built-in sub-generators (`CreateDtoGenerator`, `EntityGenerator`, ...) |
420
+
421
+ ```ts
422
+ // example: permissive IsBoolean that also accepts "true" / 1 from query strings
423
+ extraValidators: from(() => import('src/common/validators'), ['IsBoolean'])
424
+ ```
425
+
426
+ ### Declaration syntax
427
+
428
+ | Form | Where | Type-safety | Example |
429
+ | ------------------------- | --------------- | ----------- | --------------------------------------------------------------------- |
430
+ | Inline `Name:path` | `schema.prisma` | none | `extraValidators = "IsUnique,IsStrongPassword:src/common/validators"` |
431
+ | `ImportDescriptor` object | `configFile` | shape only | `{ from: 'src/common/decorators', names: ['Trim'] }` |
432
+ | `from()` helpers | `configFile` | full | `from(() => import('src/common/decorators'), ['Trim'])` |
433
+
434
+ Inline groups: `Foo,Bar:path` (named), `* as X:path` (namespace), `default as X:path` (default); join multiple with `|`. The closure form `from(() => import('path'), names)` validates both the path and the named exports at compile time (the closure is never invoked at runtime, so module side effects never fire).
435
+
436
+ ## External config file
437
+
438
+ ```prisma
439
+ generator nestjsDto {
440
+ provider = "prisma-generator-nestjs-dto"
441
+ output = "../generated"
442
+ configFile = "../nestjs-dto.config.ts"
443
+ }
444
+ ```
445
+
446
+ ```ts
447
+ // nestjs-dto.config.ts
448
+ import { from, fromNamespace, type GeneratorConfigFile } from '@tommasomeli/prisma-generator-nestjs-dto';
449
+
450
+ export default {
451
+ extraValidators: from(() => import('src/common/validators'), ['IsUnique', 'IsStrongPassword']),
452
+ extraImports: [
453
+ fromNamespace('src/common/constants', 'CONSTANTS'),
454
+ from(() => import('src/users/user.fixtures'), 'USER_EXAMPLE')
455
+ ],
456
+ extraDecorators: from(() => import('src/common/decorators'), ['Trim', 'Sanitize']),
457
+ extraAnnotations: ['DtoAuditable', 'DtoIndexed']
458
+ } satisfies GeneratorConfigFile;
459
+ ```
460
+
461
+ - Path resolved against the schema location first, then the cwd. `.ts` / `.cts` / `.mts` use [`jiti`](https://github.com/unjs/jiti) (no precompilation); `.js` / `.cjs` / `.mjs` / `.json` use `require`.
462
+ - `export default` or `export const config = ...` are both accepted. Values override the schema for the same key.
463
+ - Relative `from` paths (`./my-validators`, `../shared/lib`) are anchored to the config file's directory and rewritten relative to each emitted file. Bare specifiers, TS path aliases (`src/...`), and absolute paths pass through unchanged.
464
+
465
+ A runnable end-to-end example (`configFile`, custom `@Auditable` annotation, TS plugin, `afterAll` aggregation) lives under [`examples/blog/`](./examples/blog).
466
+
467
+ ## Annotations
468
+
469
+ Triple-slash comments (`///`) above a model or field. Multiple annotations per target are fine.
470
+
471
+ ### Built-in
472
+
473
+
474
+ | Target | Annotation | Effect |
475
+ | ------ | ------------------------------------------- | ------------------------------------------------------------------------------------- |
476
+ | model | `@DtoIgnoreModel` | Skip the model entirely. |
477
+ | model | `@DtoApiExtraModels(A, B)` | Emit `@ApiExtraModels(A, B)` on the entity class. |
478
+ | field | `@DtoReadOnly` | Exclude from Create and Update DTOs. |
479
+ | field | `@DtoCreateHidden` / `@DtoUpdateHidden` | Hide in Create / Update DTOs. |
480
+ | field | `@DtoEntityHidden` | Hide in the Entity DTO (= API response). |
481
+ | field | `@DtoApiHidden` | Emit `@ApiHideProperty()` and exclude from the Entity DTO. |
482
+ | field | `@DtoHidden` | Hide everywhere. |
483
+ | field | `@DtoCreateOptional` / `@DtoCreateRequired` | Force the field's presence/optionality in Create DTOs. |
484
+ | field | `@DtoUpdateOptional` / `@DtoUpdateRequired` | Same, for Update DTOs. |
485
+ | field | `@DtoOverrideType(<Type>)` | Override the TypeScript type. Auto-imports the matching generated model DTO if found. |
486
+ | field | `@DtoOverrideApiPropertyType(<Type>)` | Override only the Swagger `type` parameter. |
487
+ | field | `@DtoCreateValidateIf(<expr>)` | Emit `class-validator`'s `@ValidateIf(<expr>)` in the Create DTO. |
488
+ | field | `@DtoUpdateValidateIf(<expr>)` | Same, for the Update DTO. |
489
+
490
+
491
+ ### Custom (read by your plugins)
492
+
493
+ Any name declared in `extraAnnotations` is parsed the same way. The `params` array is pre-parsed: bare identifiers stay strings, numeric literals become numbers, `{ ... }` blocks stay as raw strings.
494
+
495
+ ```prisma
496
+ /// @DtoAuditable("user_audit")
497
+ /// @DtoIndexed(5, { strategy: 'btree' })
498
+ model User { ... }
499
+ ```
500
+
501
+ Inside a plugin:
502
+
503
+ ```ts
504
+ const auditable = this.getAnnotation(model, 'DtoAuditable');
505
+ // auditable?.params[0] === 'user_audit'
506
+
507
+ const indexed = this.getAnnotation(model, 'DtoIndexed');
508
+ // indexed?.params === [5, "{ strategy: 'btree' }"]
509
+ ```
510
+
511
+ ## Manifest output (opt-in)
512
+
513
+ `emitManifest = "true"` emits two extra files in `output`:
514
+
515
+ - `manifest.ts` — `PrismaManifest: Record<Prisma.ModelName, { primaryKey, entityFields, relations }>`. Useful for `select` builders, audit middleware, RBAC field lists.
516
+ - `model-entity-map.ts` — type-only `ModelEntityMap: Prisma.ModelName -> EntityClass`. Useful for typing dynamic select paths.
517
+
518
+ Both honour `fileNamingStrategy` and `outputStructure`.
519
+
520
+ ## Comparison
521
+
522
+ How this generator stacks up against the other NestJS-oriented DTO generators in the Prisma ecosystem (typical feature set across the most popular alternatives at the time of writing):
523
+
524
+ | | this | other Prisma NestJS DTO generators |
525
+ | ------------------------------------------------------------ | ---- | ---------------------------------- |
526
+ | Create / Update / Entity DTOs | yes | yes |
527
+ | Swagger / `class-validator` decorators | yes | yes |
528
+ | Annotations for hide / readonly / optional / type override | yes | partial |
529
+ | Pluggable sub-generators (`extraGenerators`) | yes | no |
530
+ | Custom annotations for plugins (`extraAnnotations`) | yes | no |
531
+ | Annotation → custom decorator / validator / namespace import | yes | partial |
532
+ | Override of built-in imports by colliding name | yes | no |
533
+ | Optional runtime manifest (`emitManifest`) | yes | no |
534
+ | Auto-import for `@DtoOverrideType` | yes | no |
535
+ | External TypeScript `configFile` with type-safe helpers | yes | no |
536
+
537
+
538
+ ## Contributing
539
+
540
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, conventions, and the PR checklist.
541
+
542
+ ```bash
543
+ npm install
544
+ npm test
545
+ npm run build
546
+ ```
547
+
548
+ Issues and PRs welcome — use the [bug](https://github.com/tommasomeli/prisma-generator-nestjs-dto/issues/new?template=bug_report.yml) or [feature](https://github.com/tommasomeli/prisma-generator-nestjs-dto/issues/new?template=feature_request.yml) templates when opening an issue.
549
+
550
+ ## License
551
+
552
+ [MIT](./LICENSE)