@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 +7 -0
- package/LICENSE +21 -0
- package/README.md +552 -0
- package/dist/bin.cjs +1469 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/index.cjs +1539 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +562 -0
- package/dist/index.d.ts +562 -0
- package/dist/index.mjs +1486 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +90 -0
package/CHANGELOG.md
ADDED
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
|
+
[](https://www.npmjs.com/package/@tommasomeli/prisma-generator-nestjs-dto)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](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)
|