Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ pnpm add prisma-class-validator-generator

1. Add the generator to your Prisma schema:

> **Heads up:** Keep a Prisma Client generator in the same schema. Both `provider = "prisma-client"` (recommended) and `provider = "prisma-client-js"` are supported.

```prisma
generator client {
provider = "prisma-client" // or "prisma-client-js"
output = "../generated/prisma-client" // required when using prisma-client
}

generator class_validator {
provider = "prisma-class-validator-generator"
output = "./generated" // optional, defaults to ./generated
Expand Down Expand Up @@ -472,4 +479,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
<a href="https://github.com/omar-dulaimi/prisma-class-validator-generator/issues">🐛 Report Issues</a> •
<a href="https://github.com/omar-dulaimi/prisma-class-validator-generator/discussions">💬 Discussions</a>
</p>
</div>
</div>
3 changes: 2 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client"
output = "../generated/prisma-client"
}

generator class_validator {
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ generatorHandler({
onManifest: () => ({
defaultOutput: './generated',
prettyName: 'Prisma Class Validator Generator',
requiresGenerators: ['prisma-client-js'],
}),
onGenerate: generate,
});
15 changes: 13 additions & 2 deletions src/prisma-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface GeneratorConfig {
separateRelationFields: boolean;
}

const SUPPORTED_PRISMA_CLIENT_PROVIDERS = new Set([
'prisma-client',
'prisma-client-js',
]);

export async function generate(options: GeneratorOptions) {
const outputDir = parseEnvValue(options.generator.output as EnvValue);

Expand All @@ -27,10 +32,16 @@ export async function generate(options: GeneratorOptions) {
await fs.mkdir(outputDir, { recursive: true });
await removeDir(outputDir, true);

const prismaClientProvider = options.otherGenerators.find(
(it) => parseEnvValue(it.provider) === 'prisma-client-js',
const prismaClientProvider = options.otherGenerators.find((it) =>
SUPPORTED_PRISMA_CLIENT_PROVIDERS.has(parseEnvValue(it.provider)),
);

if (!prismaClientProvider) {
throw new Error(
'Prisma Class Validator Generator requires a Prisma Client generator with provider "prisma-client" or "prisma-client-js".',
);
}

const prismaClientDmmf = await getDMMF({
datamodel: options.datamodel,
previewFeatures: prismaClientProvider?.previewFeatures || [],
Expand Down
14 changes: 6 additions & 8 deletions tests/basic-generation.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync, readFileSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';

const execAsync = promisify(exec);
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

describe('Basic Class Validator Generation', () => {
beforeAll(async () => {
// Build the generator first
await execAsync('npm run build');
await ensureGeneratorBuilt();
}, 60000);

it('should generate basic models with class validators', async () => {
const schemaPath = path.resolve(__dirname, 'schemas/basic.prisma');
const outputPath = path.resolve(__dirname, 'generated/basic');

// Generate using the basic schema
await execAsync(`npx prisma generate --schema="${schemaPath}"`);
await runPrismaGenerate({ schemaPath });

// Check if generated files exist
expect(existsSync(path.join(outputPath, 'models', 'User.model.ts'))).toBe(
Expand Down
14 changes: 6 additions & 8 deletions tests/comprehensive-schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync, readFileSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';

const execAsync = promisify(exec);
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

describe('Comprehensive Schema Generation', () => {
beforeAll(async () => {
// Build the generator first
await execAsync('npm run build');
await ensureGeneratorBuilt();
}, 60000);

it('should generate comprehensive models with enums and relations', async () => {
const schemaPath = path.resolve(__dirname, 'schemas/comprehensive.prisma');
const outputPath = path.resolve(__dirname, 'generated/comprehensive');

// Generate using the comprehensive schema
await execAsync(`npx prisma generate --schema="${schemaPath}"`);
await runPrismaGenerate({ schemaPath });

// Check if generated files exist
expect(existsSync(path.join(outputPath, 'models', 'User.model.ts'))).toBe(
Expand Down
33 changes: 33 additions & 0 deletions tests/prisma-client-js-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { existsSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

const schemaPath = path.resolve(
__dirname,
'schemas/prisma-client-js-provider.prisma',
);
const classValidatorOutput = path.resolve(
__dirname,
'generated/providers/prisma-client-js-class-validator',
);

describe('Prisma Client JS generator provider', () => {
beforeAll(async () => {
await ensureGeneratorBuilt();
}, 60000);

it('generates artifacts when using provider = "prisma-client-js"', async () => {
await runPrismaGenerate({ schemaPath });

expect(
existsSync(path.join(classValidatorOutput, 'models', 'User.model.ts')),
).toBe(true);
expect(
existsSync(path.join(classValidatorOutput, 'helpers', 'index.ts')),
).toBe(true);
});
});
38 changes: 38 additions & 0 deletions tests/prisma-client-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { existsSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

const schemaPath = path.resolve(
__dirname,
'schemas/prisma-client-provider.prisma',
);
const classValidatorOutput = path.resolve(
__dirname,
'generated/providers/prisma-client-class-validator',
);
const prismaClientOutput = path.resolve(
__dirname,
'generated/providers/prisma-client',
);

describe('Prisma Client generator provider', () => {
beforeAll(async () => {
await ensureGeneratorBuilt();
}, 60000);

it('generates artifacts when using provider = "prisma-client"', async () => {
await runPrismaGenerate({ schemaPath });

expect(
existsSync(path.join(classValidatorOutput, 'models', 'User.model.ts')),
).toBe(true);
expect(
existsSync(path.join(classValidatorOutput, 'helpers', 'index.ts')),
).toBe(true);
expect(existsSync(prismaClientOutput)).toBe(true);
});
});
15 changes: 6 additions & 9 deletions tests/relation-splitting.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync, readFileSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';

const execAsync = promisify(exec);
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

describe('Relation Splitting Generation', () => {
beforeAll(async () => {
// Build the generator first
await execAsync('npm run build');

// Generate models for full-features schema
await ensureGeneratorBuilt();
const schemaPath = path.resolve(__dirname, 'schemas/full-features.prisma');
await execAsync(`npx prisma generate --schema="${schemaPath}"`);
await runPrismaGenerate({ schemaPath });
}, 60000);

it('should generate separate base and relation classes', () => {
Expand Down
5 changes: 3 additions & 2 deletions tests/schemas/basic.prisma
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
provider = "prisma-client"
output = "../generated/basic-client"
}

generator class_validator {
Expand Down Expand Up @@ -30,4 +31,4 @@ model Post {
author User? @relation(fields: [authorId], references: [id])
authorId Int?
rating Float
}
}
18 changes: 18 additions & 0 deletions tests/schemas/prisma-client-js-provider.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
generator client {
provider = "prisma-client-js"
}

generator class_validator {
provider = "node ./lib/generator.js"
output = "../generated/providers/prisma-client-js-class-validator"
}

datasource db {
provider = "sqlite"
url = "file:./provider-client-js.db"
}

model User {
id Int @id @default(autoincrement())
email String @unique
}
19 changes: 19 additions & 0 deletions tests/schemas/prisma-client-provider.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
generator client {
provider = "prisma-client"
output = "../generated/providers/prisma-client"
}

generator class_validator {
provider = "node ./lib/generator.js"
output = "../generated/providers/prisma-client-class-validator"
}

datasource db {
provider = "sqlite"
url = "file:./provider-client.db"
}

model User {
id Int @id @default(autoincrement())
email String @unique
}
15 changes: 6 additions & 9 deletions tests/self-relation.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync, readFileSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';

const execAsync = promisify(exec);
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

describe('Self-Relation Generation', () => {
beforeAll(async () => {
// Build the generator first
await execAsync('npm run build');

// Generate models for self-relation schema
await ensureGeneratorBuilt();
const schemaPath = path.resolve(__dirname, 'schemas/self-relation.prisma');
await execAsync(`npx prisma generate --schema="${schemaPath}"`);
await runPrismaGenerate({ schemaPath });
}, 60000);

it('should generate UserBase class without self-relations', () => {
Expand Down
11 changes: 11 additions & 0 deletions tests/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { beforeEach, afterEach } from 'vitest';

const baseEnv = { ...process.env };

beforeEach(() => {
process.env = { ...baseEnv } as NodeJS.ProcessEnv;
});

afterEach(() => {
process.env = { ...baseEnv } as NodeJS.ProcessEnv;
});
22 changes: 8 additions & 14 deletions tests/swagger-generation.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync, readFileSync } from 'fs';
import { describe, it, expect, beforeAll } from 'vitest';
import path from 'path';

const execAsync = promisify(exec);
import {
ensureGeneratorBuilt,
runPrismaGenerate,
} from './utils/prisma-test-helpers';

describe('Swagger Generation', () => {
beforeAll(async () => {
// Build the generator first
await execAsync('npm run build');

// Generate models for swagger schema
await ensureGeneratorBuilt();
const schemaPath = path.resolve(__dirname, 'schemas/swagger.prisma');
await execAsync(`npx prisma generate --schema="${schemaPath}"`);
await runPrismaGenerate({ schemaPath });
}, 60000);

it('should generate models with Swagger decorators when enabled', () => {
Expand Down Expand Up @@ -73,12 +70,9 @@ describe('Swagger Generation', () => {

describe('Combined Class with separateRelationFields', () => {
beforeAll(async () => {
// Build the generator first
await execAsync('npm run build');

// Generate models for full-features schema (has both swagger and separateRelationFields)
await ensureGeneratorBuilt();
const schemaPath = path.resolve(__dirname, 'schemas/full-features.prisma');
await execAsync(`npx prisma generate --schema="${schemaPath}"`);
await runPrismaGenerate({ schemaPath });
}, 60000);

it('should generate combined class with class-validator and Swagger decorators for relations', () => {
Expand Down
25 changes: 25 additions & 0 deletions tests/utils/prisma-test-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

let buildPromise: Promise<void> | null = null;

export async function ensureGeneratorBuilt() {
if (!buildPromise) {
buildPromise = execAsync('npm run build');
}
return buildPromise;
}

interface GenerateOptions {
schemaPath: string;
env?: NodeJS.ProcessEnv;
}

export async function runPrismaGenerate(options: GenerateOptions) {
const { schemaPath, env } = options;
await execAsync(`npx prisma generate --schema="${schemaPath}"`, {
env: { ...process.env, ...env },
});
}
Loading