@pegasusheavy/nestjs-prisma-graphql 1.0.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/README.md ADDED
@@ -0,0 +1,1235 @@
1
+ # @pegasus-heavy/nestjs-prisma-graphql
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@pegasus-heavy/nestjs-prisma-graphql.svg)](https://www.npmjs.com/package/@pegasus-heavy/nestjs-prisma-graphql)
4
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+ [![Node.js Version](https://img.shields.io/node/v/@pegasus-heavy/nestjs-prisma-graphql.svg)](https://nodejs.org)
6
+ [![Prisma](https://img.shields.io/badge/Prisma-7.x-2D3748?logo=prisma)](https://www.prisma.io)
7
+ [![NestJS](https://img.shields.io/badge/NestJS-11.x-E0234E?logo=nestjs)](https://nestjs.com)
8
+
9
+ Generate object types, inputs, args, enums, and more from your Prisma schema for seamless integration with `@nestjs/graphql`.
10
+
11
+ **ESM-first build** — This is an ESM-native fork of [prisma-nestjs-graphql](https://github.com/unlight/prisma-nestjs-graphql), built from the ground up for ESM as the primary module format, with full Prisma 7+ compatibility.
12
+
13
+ ---
14
+
15
+ ## Table of Contents
16
+
17
+ - [Features](#features)
18
+ - [Requirements](#requirements)
19
+ - [Installation](#installation)
20
+ - [Quick Start](#quick-start)
21
+ - [Generator Options](#generator-options)
22
+ - [Core Options](#core-options)
23
+ - [Code Generation Options](#code-generation-options)
24
+ - [Type Customization Options](#type-customization-options)
25
+ - [Field Decorators](#field-decorators)
26
+ - [@HideField](#hidefield)
27
+ - [@FieldType](#fieldtype)
28
+ - [@PropertyType](#propertytype)
29
+ - [@ObjectType](#objecttype)
30
+ - [@Directive](#directive)
31
+ - [Deprecation](#deprecation)
32
+ - [Complexity](#complexity)
33
+ - [Validation with class-validator](#validation-with-class-validator)
34
+ - [Custom Decorators](#custom-decorators)
35
+ - [GraphQL Scalars](#graphql-scalars)
36
+ - [Built-in Scalar Mappings](#built-in-scalar-mappings)
37
+ - [Custom Scalar Configuration](#custom-scalar-configuration)
38
+ - [ESM Compatibility](#esm-compatibility)
39
+ - [Handling Circular Dependencies](#handling-circular-dependencies)
40
+ - [Type Registry](#type-registry)
41
+ - [Integration Examples](#integration-examples)
42
+ - [Express + Apollo (Default)](#express--apollo-default)
43
+ - [Fastify + Mercurius](#fastify--mercurius)
44
+ - [Fastify + Apollo](#fastify--apollo)
45
+ - [Resolver Example](#resolver-example)
46
+ - [Using with Prisma Service](#using-with-prisma-service)
47
+ - [Generated File Structure](#generated-file-structure)
48
+ - [Troubleshooting](#troubleshooting)
49
+ - [Development](#development)
50
+ - [Contributing](#contributing)
51
+ - [License](#license)
52
+
53
+ ---
54
+
55
+ ## Features
56
+
57
+ - 🚀 **ESM-first** — Built with ESM as the primary module format using `lodash-es` and proper `.js` extensions
58
+ - 📦 **Prisma 7+ compatible** — Full support for latest Prisma versions and features
59
+ - 🔄 **Circular dependency handling** — Built-in support for ESM circular import resolution with type registry
60
+ - 🏗️ **NestJS GraphQL ready** — Generates ready-to-use `@nestjs/graphql` decorators and types
61
+ - ⚡ **TypeScript 5.x** — Full support for latest TypeScript features with `NodeNext` module resolution
62
+ - ✅ **class-validator integration** — First-class support for validation decorators
63
+ - 🎯 **Customizable output** — Flexible file patterns, selective generation, and re-export options
64
+ - 🔧 **Extensible** — Custom scalars, decorators, and field type overrides
65
+ - 🏎️ **Express & Fastify** — Works with both Express and Fastify NestJS platforms (Apollo & Mercurius)
66
+
67
+ ---
68
+
69
+ ## Requirements
70
+
71
+ - **Node.js** >= 20.0.0
72
+ - **Prisma** >= 7.0.0
73
+ - **@nestjs/graphql** >= 12.0.0
74
+ - **TypeScript** >= 5.0.0
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ # Using pnpm (recommended)
82
+ pnpm add -D @pegasus-heavy/nestjs-prisma-graphql
83
+
84
+ # Using npm
85
+ npm install -D @pegasus-heavy/nestjs-prisma-graphql
86
+
87
+ # Using yarn
88
+ yarn add -D @pegasus-heavy/nestjs-prisma-graphql
89
+ ```
90
+
91
+ ### Peer Dependencies
92
+
93
+ Ensure you have the required peer dependencies installed:
94
+
95
+ ```bash
96
+ pnpm add @nestjs/graphql prisma @prisma/client graphql
97
+ ```
98
+
99
+ ### Optional Dependencies
100
+
101
+ For `Decimal` and `Json` field types:
102
+
103
+ ```bash
104
+ pnpm add graphql-type-json prisma-graphql-type-decimal
105
+ ```
106
+
107
+ For validation support:
108
+
109
+ ```bash
110
+ pnpm add class-validator class-transformer
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Quick Start
116
+
117
+ ### 1. Add the Generator to Your Schema
118
+
119
+ ```prisma
120
+ // schema.prisma
121
+ datasource db {
122
+ provider = "postgresql"
123
+ url = env("DATABASE_URL")
124
+ }
125
+
126
+ generator client {
127
+ provider = "prisma-client-js"
128
+ }
129
+
130
+ generator nestgraphql {
131
+ provider = "nestjs-prisma-graphql"
132
+ output = "../src/@generated"
133
+ esmCompatible = true
134
+ }
135
+
136
+ model User {
137
+ id String @id @default(cuid())
138
+ email String @unique
139
+ name String?
140
+ posts Post[]
141
+ createdAt DateTime @default(now())
142
+ updatedAt DateTime @updatedAt
143
+ }
144
+
145
+ model Post {
146
+ id String @id @default(cuid())
147
+ title String
148
+ content String?
149
+ published Boolean @default(false)
150
+ author User? @relation(fields: [authorId], references: [id])
151
+ authorId String?
152
+ createdAt DateTime @default(now())
153
+ updatedAt DateTime @updatedAt
154
+ }
155
+ ```
156
+
157
+ ### 2. Generate Types
158
+
159
+ ```bash
160
+ npx prisma generate
161
+ ```
162
+
163
+ ### 3. Use Generated Types
164
+
165
+ ```typescript
166
+ import { Resolver, Query, Args, Mutation } from '@nestjs/graphql';
167
+ import { User } from './@generated/user/user.model';
168
+ import { FindManyUserArgs } from './@generated/user/find-many-user.args';
169
+ import { UserCreateInput } from './@generated/user/user-create.input';
170
+
171
+ @Resolver(() => User)
172
+ export class UserResolver {
173
+ @Query(() => [User])
174
+ async users(@Args() args: FindManyUserArgs): Promise<User[]> {
175
+ // Your implementation
176
+ }
177
+
178
+ @Mutation(() => User)
179
+ async createUser(@Args('data') data: UserCreateInput): Promise<User> {
180
+ // Your implementation
181
+ }
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Generator Options
188
+
189
+ ### Core Options
190
+
191
+ | Option | Type | Default | Description |
192
+ |--------|------|---------|-------------|
193
+ | `output` | `string` | *required* | Output folder relative to the schema file |
194
+ | `outputFilePattern` | `string` | `{model}/{name}.{type}.ts` | Pattern for generated file paths |
195
+ | `esmCompatible` | `boolean` | `true` | Enable ESM circular import resolution |
196
+ | `prismaClientImport` | `string` | `@prisma/client` | Custom path to Prisma Client |
197
+ | `tsConfigFilePath` | `string` | - | Path to tsconfig.json for type checking |
198
+ | `disabled` | `boolean` | `false` | Disable generation (can also use env vars) |
199
+
200
+ ### Disabling the Generator
201
+
202
+ You can disable the generator using environment variables or the `disabled` config option. This is useful for CI environments, build optimization, or conditional generation.
203
+
204
+ #### Environment Variables
205
+
206
+ ```bash
207
+ # Disable this specific generator (most specific)
208
+ DISABLE_NESTJS_PRISMA_GRAPHQL=true npx prisma generate
209
+
210
+ # CI-specific skip flag
211
+ CI_SKIP_PRISMA_GRAPHQL=true npx prisma generate
212
+
213
+ # Skip all Prisma generators (common convention)
214
+ PRISMA_GENERATOR_SKIP=true npx prisma generate
215
+
216
+ # Alternative skip flag
217
+ SKIP_PRISMA_GENERATE=true npx prisma generate
218
+ ```
219
+
220
+ All environment variables accept `true` or `1` as valid values to disable the generator.
221
+
222
+ #### Config Option
223
+
224
+ ```prisma
225
+ generator nestgraphql {
226
+ provider = "nestjs-prisma-graphql"
227
+ output = "../src/@generated"
228
+ disabled = true // Skip generation (accepts: true, "true", "1", "yes")
229
+ }
230
+ ```
231
+
232
+ #### Priority Order
233
+
234
+ 1. **Environment variables** are checked first (in order of specificity)
235
+ 2. **Config option** is checked if no environment variable disables the generator
236
+
237
+ #### CI/CD Examples
238
+
239
+ ```yaml
240
+ # GitHub Actions - skip generation in certain jobs
241
+ - name: Generate Prisma Client Only
242
+ run: npx prisma generate
243
+ env:
244
+ DISABLE_NESTJS_PRISMA_GRAPHQL: true
245
+
246
+ # Docker build - skip during build, generate at runtime
247
+ ARG SKIP_CODEGEN=false
248
+ ENV CI_SKIP_PRISMA_GRAPHQL=$SKIP_CODEGEN
249
+ ```
250
+
251
+ When disabled, the generator will output:
252
+ ```
253
+ ⏭️ nestjs-prisma-graphql: Generation skipped (disabled via environment variable or config)
254
+ ```
255
+
256
+ #### Output File Pattern Variables
257
+
258
+ | Variable | Description |
259
+ |----------|-------------|
260
+ | `{model}` | Model name in different cases |
261
+ | `{name}` | Type/class name |
262
+ | `{type}` | File type (model, input, args, enum, output) |
263
+ | `{plural.type}` | Pluralized file type |
264
+
265
+ **Examples:**
266
+
267
+ ```prisma
268
+ # Default: src/@generated/user/user-create.input.ts
269
+ outputFilePattern = "{model}/{name}.{type}.ts"
270
+
271
+ # Flat structure: src/@generated/user-create.input.ts
272
+ outputFilePattern = "{name}.{type}.ts"
273
+
274
+ # By type: src/@generated/inputs/user-create.input.ts
275
+ outputFilePattern = "{plural.type}/{name}.{type}.ts"
276
+ ```
277
+
278
+ ### Code Generation Options
279
+
280
+ | Option | Type | Default | Description |
281
+ |--------|------|---------|-------------|
282
+ | `combineScalarFilters` | `boolean` | `false` | Combine nested/nullable scalar filters into single types |
283
+ | `noAtomicOperations` | `boolean` | `false` | Remove atomic operation input types (IntFieldUpdateOperationsInput, etc.) |
284
+ | `reExport` | `enum` | `None` | Create index.ts barrel files |
285
+ | `emitSingle` | `boolean` | `false` | Generate all types in a single file |
286
+ | `emitCompiled` | `boolean` | `false` | Emit compiled JavaScript alongside TypeScript |
287
+ | `purgeOutput` | `boolean` | `false` | Delete output folder before generating |
288
+ | `emitBlocks` | `string[]` | all | Selective generation: `enums`, `models`, `inputs`, `outputs`, `args` |
289
+ | `requireSingleFieldsInWhereUniqueInput` | `boolean` | `false` | Make unique fields required in WhereUniqueInput |
290
+
291
+ #### reExport Options
292
+
293
+ | Value | Description |
294
+ |-------|-------------|
295
+ | `None` | No barrel files |
296
+ | `Directories` | Create index.ts in each directory |
297
+ | `Single` | Create single index.ts at output root |
298
+ | `All` | Create index.ts in all directories and root |
299
+
300
+ ### Type Customization Options
301
+
302
+ | Option | Type | Default | Description |
303
+ |--------|------|---------|-------------|
304
+ | `noTypeId` | `boolean` | `false` | Use `String` instead of `ID` for @id fields |
305
+ | `omitModelsCount` | `boolean` | `false` | Omit `_count` field from model types |
306
+ | `useInputType` | `string` | - | Pattern for selecting input types |
307
+
308
+ ---
309
+
310
+ ## Field Decorators
311
+
312
+ Use triple-slash comments (`///`) in your Prisma schema to add decorators and customize generated code.
313
+
314
+ ### @HideField
315
+
316
+ Hide fields from the GraphQL schema. Useful for sensitive data like passwords.
317
+
318
+ ```prisma
319
+ model User {
320
+ id String @id @default(cuid())
321
+ email String @unique
322
+
323
+ /// @HideField()
324
+ password String
325
+
326
+ /// @HideField({ input: true, output: false })
327
+ internalId String?
328
+
329
+ /// @HideField({ match: '*Password*' })
330
+ tempPassword String?
331
+ }
332
+ ```
333
+
334
+ **Options:**
335
+
336
+ | Option | Type | Description |
337
+ |--------|------|-------------|
338
+ | `input` | `boolean` | Hide from input types |
339
+ | `output` | `boolean` | Hide from output types (default: `true`) |
340
+ | `match` | `string \| string[]` | Glob pattern(s) for field name matching |
341
+
342
+ ### @FieldType
343
+
344
+ Override the GraphQL field type.
345
+
346
+ ```prisma
347
+ model User {
348
+ /// @FieldType('Scalars.GraphQLEmailAddress')
349
+ email String @unique
350
+
351
+ /// @FieldType({ name: 'GraphQLURL', from: 'graphql-scalars', input: true })
352
+ website String?
353
+
354
+ /// @FieldType({ name: 'CustomType', match: 'input' })
355
+ data Json?
356
+ }
357
+ ```
358
+
359
+ **Options:**
360
+
361
+ | Option | Type | Description |
362
+ |--------|------|-------------|
363
+ | `name` | `string` | Type name (required) |
364
+ | `from` | `string` | Import module specifier |
365
+ | `input` | `boolean` | Apply to input types |
366
+ | `output` | `boolean` | Apply to output types |
367
+ | `match` | `string` | Glob pattern for type name matching |
368
+
369
+ ### @PropertyType
370
+
371
+ Override the TypeScript property type.
372
+
373
+ ```prisma
374
+ model User {
375
+ /// @PropertyType('Buffer')
376
+ avatar Bytes?
377
+
378
+ /// @PropertyType({ name: 'MyCustomClass', from: './custom-class' })
379
+ metadata Json?
380
+ }
381
+ ```
382
+
383
+ ### @ObjectType
384
+
385
+ Customize the generated `@ObjectType()` decorator.
386
+
387
+ ```prisma
388
+ /// @ObjectType({ isAbstract: true })
389
+ model BaseEntity {
390
+ id String @id @default(cuid())
391
+ createdAt DateTime @default(now())
392
+ updatedAt DateTime @updatedAt
393
+ }
394
+
395
+ /// @ObjectType('UserProfile')
396
+ model User {
397
+ id String @id
398
+ name String
399
+ }
400
+ ```
401
+
402
+ ### @Directive
403
+
404
+ Add custom GraphQL directives.
405
+
406
+ ```prisma
407
+ model User {
408
+ /// @Directive('@auth(requires: ADMIN)')
409
+ adminField String?
410
+
411
+ /// @Directive('@deprecated(reason: "Use newField instead")')
412
+ oldField String?
413
+ }
414
+ ```
415
+
416
+ ### Deprecation
417
+
418
+ Mark fields as deprecated.
419
+
420
+ ```prisma
421
+ model User {
422
+ /// @deprecated Use 'email' instead
423
+ username String?
424
+
425
+ email String @unique
426
+ }
427
+ ```
428
+
429
+ ### Complexity
430
+
431
+ Set field complexity for query cost analysis.
432
+
433
+ ```prisma
434
+ model User {
435
+ id String @id
436
+
437
+ /// @complexity 5
438
+ posts Post[]
439
+
440
+ /// @complexity 10
441
+ followers User[]
442
+ }
443
+ ```
444
+
445
+ ---
446
+
447
+ ## Validation with class-validator
448
+
449
+ First-class support for `class-validator` decorators on generated input types.
450
+
451
+ ### Configuration
452
+
453
+ Add the validator configuration to your generator:
454
+
455
+ ```prisma
456
+ generator nestgraphql {
457
+ provider = "nestjs-prisma-graphql"
458
+ output = "../src/@generated"
459
+ fields_Validator_from = "class-validator"
460
+ fields_Validator_input = true
461
+ }
462
+ ```
463
+
464
+ ### Usage in Schema
465
+
466
+ ```prisma
467
+ model User {
468
+ id String @id @default(cuid())
469
+
470
+ /// @Validator.IsEmail()
471
+ /// @Validator.MaxLength(255)
472
+ email String @unique
473
+
474
+ /// @Validator.MinLength(2)
475
+ /// @Validator.MaxLength(100)
476
+ name String
477
+
478
+ /// @Validator.IsOptional()
479
+ /// @Validator.IsUrl()
480
+ website String?
481
+
482
+ /// @Validator.Min(0)
483
+ /// @Validator.Max(150)
484
+ age Int?
485
+
486
+ /// @Validator.IsPhoneNumber('US')
487
+ phone String?
488
+
489
+ /// @Validator.Matches(/^[a-zA-Z0-9_]+$/)
490
+ username String @unique
491
+ }
492
+ ```
493
+
494
+ ### Generated Output
495
+
496
+ ```typescript
497
+ import { IsEmail, MaxLength, MinLength, IsOptional, IsUrl } from 'class-validator';
498
+
499
+ @InputType()
500
+ export class UserCreateInput {
501
+ @IsEmail()
502
+ @MaxLength(255)
503
+ @Field(() => String)
504
+ email!: string;
505
+
506
+ @MinLength(2)
507
+ @MaxLength(100)
508
+ @Field(() => String)
509
+ name!: string;
510
+
511
+ @IsOptional()
512
+ @IsUrl()
513
+ @Field(() => String, { nullable: true })
514
+ website?: string;
515
+ }
516
+ ```
517
+
518
+ ### Available Validators
519
+
520
+ All `class-validator` decorators are supported:
521
+
522
+ **String Validators:**
523
+ - `@Validator.IsEmail()`
524
+ - `@Validator.IsUrl()`
525
+ - `@Validator.IsUUID()`
526
+ - `@Validator.MinLength(n)`
527
+ - `@Validator.MaxLength(n)`
528
+ - `@Validator.Matches(regex)`
529
+ - `@Validator.IsAlpha()`
530
+ - `@Validator.IsAlphanumeric()`
531
+
532
+ **Number Validators:**
533
+ - `@Validator.Min(n)`
534
+ - `@Validator.Max(n)`
535
+ - `@Validator.IsPositive()`
536
+ - `@Validator.IsNegative()`
537
+ - `@Validator.IsInt()`
538
+
539
+ **Type Validators:**
540
+ - `@Validator.IsBoolean()`
541
+ - `@Validator.IsDate()`
542
+ - `@Validator.IsArray()`
543
+ - `@Validator.IsObject()`
544
+
545
+ **General:**
546
+ - `@Validator.IsOptional()`
547
+ - `@Validator.IsNotEmpty()`
548
+ - `@Validator.IsDefined()`
549
+ - `@Validator.ValidateNested()`
550
+
551
+ ---
552
+
553
+ ## Custom Decorators
554
+
555
+ Define custom decorator namespaces for your own decorators or third-party libraries.
556
+
557
+ ### Configuration
558
+
559
+ ```prisma
560
+ generator nestgraphql {
561
+ provider = "nestjs-prisma-graphql"
562
+ output = "../src/@generated"
563
+
564
+ # class-transformer decorators
565
+ fields_Transform_from = "class-transformer"
566
+ fields_Transform_input = true
567
+
568
+ # Custom decorators
569
+ fields_Custom_from = "./decorators"
570
+ fields_Custom_input = true
571
+ fields_Custom_output = true
572
+ }
573
+ ```
574
+
575
+ ### Usage
576
+
577
+ ```prisma
578
+ model User {
579
+ /// @Transform.Type(() => Date)
580
+ /// @Transform.Transform(({ value }) => new Date(value))
581
+ birthDate DateTime?
582
+
583
+ /// @Custom.Sanitize()
584
+ bio String?
585
+ }
586
+ ```
587
+
588
+ ---
589
+
590
+ ## GraphQL Scalars
591
+
592
+ ### Built-in Scalar Mappings
593
+
594
+ | Prisma Type | GraphQL Type | TypeScript Type |
595
+ |-------------|--------------|-----------------|
596
+ | `String` | `String` | `string` |
597
+ | `Int` | `Int` | `number` |
598
+ | `Float` | `Float` | `number` |
599
+ | `Boolean` | `Boolean` | `boolean` |
600
+ | `DateTime` | `Date` | `Date \| string` |
601
+ | `Json` | `GraphQLJSON` | `any` |
602
+ | `Decimal` | `GraphQLDecimal` | `Decimal` |
603
+ | `BigInt` | `BigInt` | `bigint \| number` |
604
+ | `Bytes` | `String` | `Uint8Array` |
605
+ | `@id` fields | `ID` | `string` |
606
+
607
+ ### Custom Scalar Configuration
608
+
609
+ Override default scalar mappings:
610
+
611
+ ```prisma
612
+ generator nestgraphql {
613
+ provider = "nestjs-prisma-graphql"
614
+ output = "../src/@generated"
615
+
616
+ # Custom DateTime scalar
617
+ graphqlScalars_DateTime_name = "GraphQLISODateTime"
618
+ graphqlScalars_DateTime_specifier = "@nestjs/graphql"
619
+
620
+ # Custom JSON scalar
621
+ graphqlScalars_Json_name = "GraphQLJSONObject"
622
+ graphqlScalars_Json_specifier = "graphql-type-json"
623
+
624
+ # Custom BigInt scalar
625
+ graphqlScalars_BigInt_name = "GraphQLBigInt"
626
+ graphqlScalars_BigInt_specifier = "graphql-scalars"
627
+ }
628
+ ```
629
+
630
+ ### Using graphql-scalars
631
+
632
+ ```prisma
633
+ generator nestgraphql {
634
+ provider = "nestjs-prisma-graphql"
635
+ output = "../src/@generated"
636
+
637
+ graphqlScalars_DateTime_name = "GraphQLDateTime"
638
+ graphqlScalars_DateTime_specifier = "graphql-scalars"
639
+ }
640
+ ```
641
+
642
+ ---
643
+
644
+ ## ESM Compatibility
645
+
646
+ This generator is built from the ground up for ESM (ECMAScript Modules).
647
+
648
+ ### Key ESM Features
649
+
650
+ - Uses `lodash-es` instead of `lodash`
651
+ - All imports include `.js` extensions
652
+ - `"type": "module"` in package.json
653
+ - `NodeNext` module resolution
654
+ - Proper `import type` for type-only imports
655
+
656
+ ### ESM vs CommonJS: The Circular Dependency Problem
657
+
658
+ In CommonJS, circular dependencies "just work" because modules receive a partial export object that gets filled in as the module executes. In ESM, imports are "live bindings" that reference the actual exported value—which may be `undefined` if the module hasn't finished initializing.
659
+
660
+ **CommonJS behavior:**
661
+ ```javascript
662
+ // user.js imports post.js, post.js imports user.js
663
+ // CJS: Both get a partial object that fills in later ✅
664
+ ```
665
+
666
+ **ESM behavior:**
667
+ ```javascript
668
+ // user.js imports post.js, post.js imports user.js
669
+ // ESM: One of them gets undefined during initialization ❌
670
+ ```
671
+
672
+ This causes bundling errors where CJS would produce valid bundles but ESM complains about broken dependency cycles.
673
+
674
+ ### The Solution: esmCompatible Mode
675
+
676
+ Enable `esmCompatible` mode to generate code that handles circular dependencies:
677
+
678
+ ```prisma
679
+ generator nestgraphql {
680
+ provider = "nestjs-prisma-graphql"
681
+ output = "../src/@generated"
682
+ esmCompatible = true
683
+ }
684
+ ```
685
+
686
+ ### Type Registry
687
+
688
+ When `esmCompatible` is enabled, the generator creates:
689
+
690
+ 1. **`type-registry.ts`** — Central registry with lazy type resolution helpers
691
+ 2. **`register-all-types.ts`** — Registers all types at application startup
692
+
693
+ #### Available Functions
694
+
695
+ | Function | Description |
696
+ |----------|-------------|
697
+ | `registerType(name, type)` | Register a type with the registry |
698
+ | `getType<T>(name)` | Get a registered type (for `@Field` decorators) |
699
+ | `forwardRef<T>(name)` | Create a forward reference with error handling |
700
+ | `lazyType<T>(name)` | Create a lazy type thunk (safest pattern) |
701
+ | `isTypeRegistered(name)` | Check if a type is registered |
702
+ | `validateRegistry(types)` | Validate expected types are registered |
703
+
704
+ #### Setup
705
+
706
+ Import the registry **first** in your application bootstrap:
707
+
708
+ ```typescript
709
+ // main.ts - register-all-types MUST be the first import!
710
+ import './@generated/register-all-types.js';
711
+
712
+ import 'reflect-metadata';
713
+ import { NestFactory } from '@nestjs/core';
714
+ import { AppModule } from './app.module.js';
715
+
716
+ async function bootstrap() {
717
+ const app = await NestFactory.create(AppModule);
718
+ await app.listen(3000);
719
+ }
720
+
721
+ bootstrap();
722
+ ```
723
+
724
+ #### How It Works
725
+
726
+ For circular references, the generator uses lazy type resolution:
727
+
728
+ ```typescript
729
+ // Instead of direct import (causes circular dependency)
730
+ import { Post } from '../post/post.model.js';
731
+
732
+ // Uses lazy resolution via type registry
733
+ import { getType } from '../type-registry.js';
734
+ import type { Post } from '../post/post.model.js'; // Type-only import is safe
735
+
736
+ @ObjectType()
737
+ export class User {
738
+ @Field(() => getType('Post'), { nullable: true })
739
+ posts?: Post[];
740
+ }
741
+
742
+ // Register this type
743
+ registerType('User', User);
744
+ ```
745
+
746
+ ### Bundler Compatibility
747
+
748
+ The ESM-compatible output works with:
749
+
750
+ | Bundler | Status | Notes |
751
+ |---------|--------|-------|
752
+ | **esbuild** | ✅ | Native ESM support |
753
+ | **Vite** | ✅ | Uses esbuild under the hood |
754
+ | **Rollup** | ✅ | With proper config |
755
+ | **webpack** | ✅ | ESM output mode |
756
+ | **tsx/ts-node** | ✅ | With ESM loader |
757
+ | **Node.js** | ✅ | v20+ with `"type": "module"` |
758
+
759
+ ### Debugging Circular Dependencies
760
+
761
+ If you see errors like "Cannot access 'X' before initialization":
762
+
763
+ 1. **Check import order**: Ensure `register-all-types.js` is imported first
764
+ 2. **Verify registration**: Use `getRegisteredTypes()` to see what's registered
765
+ 3. **Validate types**: Use `validateRegistry(['User', 'Post'])` to check expected types
766
+
767
+ ```typescript
768
+ import { getRegisteredTypes, validateRegistry } from './@generated/type-registry.js';
769
+
770
+ // Debug: see what's registered
771
+ console.log('Registered types:', getRegisteredTypes());
772
+
773
+ // Validate expected types exist
774
+ validateRegistry(['User', 'Post', 'Comment']);
775
+ ```
776
+
777
+ ---
778
+
779
+ ## Integration Examples
780
+
781
+ This library works with both **Express** (default) and **Fastify** NestJS applications.
782
+
783
+ ### Express + Apollo (Default)
784
+
785
+ ```typescript
786
+ // app.module.ts
787
+ import { Module } from '@nestjs/common';
788
+ import { GraphQLModule } from '@nestjs/graphql';
789
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
790
+ import { join } from 'path';
791
+
792
+ @Module({
793
+ imports: [
794
+ GraphQLModule.forRoot<ApolloDriverConfig>({
795
+ driver: ApolloDriver,
796
+ autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
797
+ sortSchema: true,
798
+ playground: true,
799
+ }),
800
+ // Your feature modules
801
+ UserModule,
802
+ PostModule,
803
+ ],
804
+ })
805
+ export class AppModule {}
806
+ ```
807
+
808
+ ```typescript
809
+ // main.ts (Express)
810
+ import 'reflect-metadata';
811
+ import './@generated/register-all-types.js';
812
+
813
+ import { NestFactory } from '@nestjs/core';
814
+ import { ValidationPipe } from '@nestjs/common';
815
+ import { AppModule } from './app.module.js';
816
+
817
+ async function bootstrap() {
818
+ const app = await NestFactory.create(AppModule);
819
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
820
+ await app.listen(3000);
821
+ }
822
+ bootstrap();
823
+ ```
824
+
825
+ ### Fastify + Mercurius
826
+
827
+ For better performance, you can use Fastify with Mercurius as the GraphQL adapter:
828
+
829
+ ```typescript
830
+ // app.module.ts
831
+ import { Module } from '@nestjs/common';
832
+ import { GraphQLModule } from '@nestjs/graphql';
833
+ import { MercuriusDriver, MercuriusDriverConfig } from '@nestjs/mercurius';
834
+ import { join } from 'path';
835
+
836
+ @Module({
837
+ imports: [
838
+ GraphQLModule.forRoot<MercuriusDriverConfig>({
839
+ driver: MercuriusDriver,
840
+ autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
841
+ sortSchema: true,
842
+ graphiql: true,
843
+ }),
844
+ // Your feature modules
845
+ UserModule,
846
+ PostModule,
847
+ ],
848
+ })
849
+ export class AppModule {}
850
+ ```
851
+
852
+ ```typescript
853
+ // main.ts (Fastify)
854
+ import 'reflect-metadata';
855
+ import './@generated/register-all-types.js';
856
+
857
+ import { NestFactory } from '@nestjs/core';
858
+ import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
859
+ import { ValidationPipe } from '@nestjs/common';
860
+ import { AppModule } from './app.module.js';
861
+
862
+ async function bootstrap() {
863
+ const app = await NestFactory.create<NestFastifyApplication>(
864
+ AppModule,
865
+ new FastifyAdapter({ logger: true }),
866
+ );
867
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
868
+ await app.listen(3000, '0.0.0.0');
869
+ }
870
+ bootstrap();
871
+ ```
872
+
873
+ ### Fastify + Apollo
874
+
875
+ You can also use Apollo Server with Fastify:
876
+
877
+ ```typescript
878
+ // app.module.ts
879
+ import { Module } from '@nestjs/common';
880
+ import { GraphQLModule } from '@nestjs/graphql';
881
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
882
+ import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
883
+ import { join } from 'path';
884
+
885
+ @Module({
886
+ imports: [
887
+ GraphQLModule.forRoot<ApolloDriverConfig>({
888
+ driver: ApolloDriver,
889
+ autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
890
+ sortSchema: true,
891
+ playground: false,
892
+ plugins: [ApolloServerPluginLandingPageLocalDefault()],
893
+ }),
894
+ UserModule,
895
+ PostModule,
896
+ ],
897
+ })
898
+ export class AppModule {}
899
+ ```
900
+
901
+ ```typescript
902
+ // main.ts (Fastify + Apollo)
903
+ import 'reflect-metadata';
904
+ import './@generated/register-all-types.js';
905
+
906
+ import { NestFactory } from '@nestjs/core';
907
+ import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
908
+ import { ValidationPipe } from '@nestjs/common';
909
+ import { AppModule } from './app.module.js';
910
+
911
+ async function bootstrap() {
912
+ const app = await NestFactory.create<NestFastifyApplication>(
913
+ AppModule,
914
+ new FastifyAdapter(),
915
+ );
916
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
917
+ await app.listen(3000, '0.0.0.0');
918
+ }
919
+ bootstrap();
920
+ ```
921
+
922
+ ### Resolver Example
923
+
924
+ ```typescript
925
+ // user.resolver.ts
926
+ import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql';
927
+ import { User } from './@generated/user/user.model.js';
928
+ import { FindManyUserArgs } from './@generated/user/find-many-user.args.js';
929
+ import { FindUniqueUserArgs } from './@generated/user/find-unique-user.args.js';
930
+ import { UserCreateInput } from './@generated/user/user-create.input.js';
931
+ import { UserUpdateInput } from './@generated/user/user-update.input.js';
932
+ import { UserWhereUniqueInput } from './@generated/user/user-where-unique.input.js';
933
+ import { PrismaService } from './prisma.service.js';
934
+
935
+ @Resolver(() => User)
936
+ export class UserResolver {
937
+ constructor(private readonly prisma: PrismaService) {}
938
+
939
+ @Query(() => [User], { name: 'users' })
940
+ async findMany(@Args() args: FindManyUserArgs): Promise<User[]> {
941
+ return this.prisma.user.findMany(args);
942
+ }
943
+
944
+ @Query(() => User, { name: 'user', nullable: true })
945
+ async findUnique(@Args() args: FindUniqueUserArgs): Promise<User | null> {
946
+ return this.prisma.user.findUnique(args);
947
+ }
948
+
949
+ @Mutation(() => User)
950
+ async createUser(@Args('data') data: UserCreateInput): Promise<User> {
951
+ return this.prisma.user.create({ data });
952
+ }
953
+
954
+ @Mutation(() => User)
955
+ async updateUser(
956
+ @Args('where') where: UserWhereUniqueInput,
957
+ @Args('data') data: UserUpdateInput,
958
+ ): Promise<User> {
959
+ return this.prisma.user.update({ where, data });
960
+ }
961
+
962
+ @Mutation(() => User)
963
+ async deleteUser(@Args('where') where: UserWhereUniqueInput): Promise<User> {
964
+ return this.prisma.user.delete({ where });
965
+ }
966
+ }
967
+ ```
968
+
969
+ ### Using with Prisma Service
970
+
971
+ ```typescript
972
+ // prisma.service.ts
973
+ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
974
+ import { PrismaClient } from '@prisma/client';
975
+
976
+ @Injectable()
977
+ export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
978
+ async onModuleInit(): Promise<void> {
979
+ await this.$connect();
980
+ }
981
+
982
+ async onModuleDestroy(): Promise<void> {
983
+ await this.$disconnect();
984
+ }
985
+ }
986
+ ```
987
+
988
+ ### With Validation Pipe
989
+
990
+ ```typescript
991
+ // main.ts
992
+ import 'reflect-metadata';
993
+ import './@generated/register-all-types.js';
994
+
995
+ import { NestFactory } from '@nestjs/core';
996
+ import { ValidationPipe } from '@nestjs/common';
997
+ import { AppModule } from './app.module.js';
998
+
999
+ async function bootstrap() {
1000
+ const app = await NestFactory.create(AppModule);
1001
+
1002
+ // Enable class-validator validation
1003
+ app.useGlobalPipes(
1004
+ new ValidationPipe({
1005
+ transform: true,
1006
+ whitelist: true,
1007
+ forbidNonWhitelisted: true,
1008
+ }),
1009
+ );
1010
+
1011
+ await app.listen(3000);
1012
+ }
1013
+
1014
+ bootstrap();
1015
+ ```
1016
+
1017
+ ---
1018
+
1019
+ ## Generated File Structure
1020
+
1021
+ With default settings, the generator creates:
1022
+
1023
+ ```
1024
+ src/@generated/
1025
+ ├── prisma/
1026
+ │ ├── sort-order.enum.ts
1027
+ │ ├── query-mode.enum.ts
1028
+ │ └── ...scalar-filters
1029
+ ├── user/
1030
+ │ ├── user.model.ts
1031
+ │ ├── user-create.input.ts
1032
+ │ ├── user-update.input.ts
1033
+ │ ├── user-where.input.ts
1034
+ │ ├── user-where-unique.input.ts
1035
+ │ ├── user-order-by.input.ts
1036
+ │ ├── find-many-user.args.ts
1037
+ │ ├── find-unique-user.args.ts
1038
+ │ ├── create-one-user.args.ts
1039
+ │ ├── update-one-user.args.ts
1040
+ │ ├── delete-one-user.args.ts
1041
+ │ └── ...
1042
+ ├── post/
1043
+ │ └── ...
1044
+ ├── type-registry.ts # ESM type registry
1045
+ ├── register-all-types.ts # Type registration
1046
+ └── index.ts # Barrel export (if reExport enabled)
1047
+ ```
1048
+
1049
+ ---
1050
+
1051
+ ## Troubleshooting
1052
+
1053
+ ### Common Issues
1054
+
1055
+ #### "Cannot find module" errors in ESM
1056
+
1057
+ Ensure all imports in your code include `.js` extensions:
1058
+
1059
+ ```typescript
1060
+ // ❌ Wrong
1061
+ import { User } from './@generated/user/user.model';
1062
+
1063
+ // ✅ Correct
1064
+ import { User } from './@generated/user/user.model.js';
1065
+ ```
1066
+
1067
+ #### Circular dependency warnings
1068
+
1069
+ Enable `esmCompatible` mode and import the type registry:
1070
+
1071
+ ```typescript
1072
+ // main.ts - import early
1073
+ import './@generated/register-all-types.js';
1074
+ ```
1075
+
1076
+ #### Type mismatch with Prisma Client
1077
+
1078
+ Ensure your generator output is excluded from tsconfig's `include`:
1079
+
1080
+ ```json
1081
+ {
1082
+ "compilerOptions": { ... },
1083
+ "include": ["src/**/*.ts"],
1084
+ "exclude": ["node_modules", "src/@generated"]
1085
+ }
1086
+ ```
1087
+
1088
+ #### Decimal type not found
1089
+
1090
+ Install the required package:
1091
+
1092
+ ```bash
1093
+ pnpm add prisma-graphql-type-decimal decimal.js
1094
+ ```
1095
+
1096
+ #### JSON type not found
1097
+
1098
+ Install the required package:
1099
+
1100
+ ```bash
1101
+ pnpm add graphql-type-json
1102
+ ```
1103
+
1104
+ ### Debugging
1105
+
1106
+ Enable verbose logging by setting the `DEBUG` environment variable:
1107
+
1108
+ ```bash
1109
+ DEBUG=prisma:generator npx prisma generate
1110
+ ```
1111
+
1112
+ ---
1113
+
1114
+ ## Development
1115
+
1116
+ ### Prerequisites
1117
+
1118
+ - Node.js >= 20.0.0
1119
+ - pnpm >= 10.0.0
1120
+
1121
+ ### Setup
1122
+
1123
+ ```bash
1124
+ # Clone the repository
1125
+ git clone https://github.com/pegasusheavy/nestjs-prisma-graphql.git
1126
+ cd nestjs-prisma-graphql
1127
+
1128
+ # Install dependencies
1129
+ pnpm install
1130
+
1131
+ # Build
1132
+ pnpm build
1133
+
1134
+ # Run tests
1135
+ pnpm test
1136
+
1137
+ # Run tests with coverage
1138
+ pnpm test:cov
1139
+
1140
+ # Type check
1141
+ pnpm typecheck
1142
+
1143
+ # Lint
1144
+ pnpm lint
1145
+
1146
+ # Format
1147
+ pnpm format
1148
+ ```
1149
+
1150
+ ### Project Structure
1151
+
1152
+ ```
1153
+ src/
1154
+ ├── handlers/ # Event handlers for code generation
1155
+ ├── helpers/ # Utility functions
1156
+ ├── generate.ts # Main generation logic
1157
+ ├── index.ts # Entry point
1158
+ ├── types.ts # Type definitions
1159
+ └── event-names.ts # Event constants
1160
+ ```
1161
+
1162
+ ### Running Tests
1163
+
1164
+ ```bash
1165
+ # Run all tests
1166
+ pnpm test
1167
+
1168
+ # Run tests in watch mode
1169
+ pnpm test:watch
1170
+
1171
+ # Run with coverage
1172
+ pnpm test:cov
1173
+ ```
1174
+
1175
+ ---
1176
+
1177
+ ## Contributing
1178
+
1179
+ Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
1180
+
1181
+ ### Commit Convention
1182
+
1183
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/):
1184
+
1185
+ - `feat:` New features
1186
+ - `fix:` Bug fixes
1187
+ - `docs:` Documentation changes
1188
+ - `style:` Code style changes (formatting, semicolons, etc.)
1189
+ - `refactor:` Code refactoring
1190
+ - `perf:` Performance improvements
1191
+ - `test:` Adding or updating tests
1192
+ - `chore:` Maintenance tasks
1193
+
1194
+ ### Pull Request Process
1195
+
1196
+ 1. Fork the repository
1197
+ 2. Create a feature branch (`git checkout -b feat/amazing-feature`)
1198
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
1199
+ 4. Push to the branch (`git push origin feat/amazing-feature`)
1200
+ 5. Open a Pull Request
1201
+
1202
+ ---
1203
+
1204
+ ## License
1205
+
1206
+ Copyright 2026 Pegasus Heavy Industries LLC
1207
+
1208
+ Licensed under the Apache License, Version 2.0 (the "License");
1209
+ you may not use this file except in compliance with the License.
1210
+ You may obtain a copy of the License at
1211
+
1212
+ http://www.apache.org/licenses/LICENSE-2.0
1213
+
1214
+ Unless required by applicable law or agreed to in writing, software
1215
+ distributed under the License is distributed on an "AS IS" BASIS,
1216
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1217
+ See the License for the specific language governing permissions and
1218
+ limitations under the License.
1219
+
1220
+ See the [LICENSE](LICENSE) file for full details.
1221
+
1222
+ ---
1223
+
1224
+ ## Acknowledgments
1225
+
1226
+ This project is a fork of [prisma-nestjs-graphql](https://github.com/unlight/prisma-nestjs-graphql) by [unlight](https://github.com/unlight). We thank the original author for their excellent work.
1227
+
1228
+ ---
1229
+
1230
+ ## Support
1231
+
1232
+ - 📖 [Documentation](https://github.com/pegasusheavy/nestjs-prisma-graphql#readme)
1233
+ - 🐛 [Issue Tracker](https://github.com/pegasusheavy/nestjs-prisma-graphql/issues)
1234
+ - 💬 [Discussions](https://github.com/pegasusheavy/nestjs-prisma-graphql/discussions)
1235
+ - ❤️ [Sponsor](https://github.com/sponsors/pegasusheavy)