Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ export class ProductDto extends IntersectionType(
- Apply a ! after non-optional class fields to avoid strict mode warnings (Property has no initializer and is not definitely assigned in the constructor.)
- _preserveDefaultNullable_
- Determines how null fields are handled. When set to **false** (default), it turns all null fields to undefined. Otherwise, it follows Prisma generation and adds null to the type.
- _nameConvention_
- Determines what naming convention to use for the generated classes' file names. The default value is **snake**. The other option is **pascal**, **camel**, **kebab**.

### **How it works?**

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@prisma/client": "^5.5.2",
"@prisma/generator-helper": "^5.5.2",
"@prisma/internals": "^5.5.2",
"change-case": "^4.1.2",
"change-case": "^5.4.4",
"prettier": "2.5.1"
},
"devDependencies": {
Expand Down
29 changes: 15 additions & 14 deletions prisma/postgresql.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ generator prismaClassGenerator {
output = "../src/_gen/prisma-class"
dryRun = "false"
separateRelationFields = "false"
nameConvention = "kebab"
}

enum ProductType {
Expand All @@ -27,7 +28,7 @@ enum ProductAnotherType {
CC
}

model Product {
model TestProduct {
id Int @id
title String @db.VarChar(255)
desc String @default("abc") @db.VarChar(1024)
Expand All @@ -39,29 +40,29 @@ model Product {
averageRating Float?
categoryId Int
companyId Int
category Category @relation(fields: [categoryId], references: [id])
company Company @relation(fields: [companyId], references: [id])
category TestCategory @relation(fields: [categoryId], references: [id])
company TestCompany @relation(fields: [companyId], references: [id])
createdAt DateTime @default(now()) @db.Timestamp(6)
updatedAt DateTime @updatedAt @db.Timestamp(6)
}

model Category {
id Int @id
products Product[]
model TestCategory {
id Int @id
products TestProduct[]
}

model Company {
id Int @id
model TestCompany {
id Int @id
name String
totalIncome BigInt @default(100)
totalIncome BigInt @default(100)
lat Decimal
lng Decimal
by Bytes
products Product[]
products TestProduct[]
tags String[]
tagsWithEmptyDefault String[] @default([])
tagsWithDefault String[] @default(["a", "b"])
tagsWithEmptyDefault String[] @default([])
tagsWithDefault String[] @default(["a", "b"])
numTags Int[]
numTagsWithEmptyDefault Int[] @default([])
numTagsWithDefault Int[] @default([1, 2])
numTagsWithEmptyDefault Int[] @default([])
numTagsWithDefault Int[] @default([1, 2])
}
24 changes: 21 additions & 3 deletions src/components/file.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { pascalCase, snakeCase } from 'change-case'
import { pascalCase, snakeCase, camelCase, kebabCase } from 'change-case'
import { ClassComponent } from './class.component'
import * as path from 'path'
import { getRelativeTSPath, prettierFormat, writeTSFile } from '../util'
import { PrismaClassGenerator } from '../generator'
import { Echoable } from '../interfaces/echoable'
import { ImportComponent } from './import.component'
import { PrismaClassGeneratorOptions } from '../interfaces/options'

export class FileComponent implements Echoable {
private _dir?: string
Expand Down Expand Up @@ -45,11 +46,28 @@ export class FileComponent implements Echoable {
this._prismaClass = value
}

constructor(input: { classComponent: ClassComponent; output: string }) {
constructor(input: {
classComponent: ClassComponent
output: string
case: PrismaClassGeneratorOptions['nameConvention']
}) {
const { classComponent, output } = input
this._prismaClass = classComponent
this.dir = path.resolve(output)
this.filename = `${snakeCase(classComponent.name)}.ts`
switch (input.case) {
case 'camel':
this.filename = `${camelCase(classComponent.name)}.ts`
break
case 'pascal':
this.filename = `${pascalCase(classComponent.name)}.ts`
break
case 'kebab':
this.filename = `${kebabCase(classComponent.name)}.ts`
break
default:
this.filename = `${snakeCase(classComponent.name)}.ts`
break
}
this.resolveImports()
}

Expand Down
23 changes: 19 additions & 4 deletions src/error-handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { Dictionary } from '@prisma/internals'
import { PrismaClassGeneratorOptions } from './generator'
import { DEFAULT_OPTIONS } from './generator'
import { log } from './util'
import { PrismaClassGeneratorOptions } from './interfaces/options'

const OPTIONS_DESCRIPTION: Record<keyof PrismaClassGeneratorOptions, string> = {
makeIndexFile: 'make index file',
dryRun: 'dry run',
separateRelationFields: 'separate relation fields',
useSwagger: 'use swagger decorstor',
useGraphQL: 'use graphql',
useUndefinedDefault: 'use undefined default',
clientImportPath: 'set prisma import path instead `@prisma/client`',
useNonNullableAssertions:
'applies non-nullable assertions (!) to class properties',
preserveDefaultNullable: 'preserve default nullable behavior',
nameConvention: 'name convention for generated classes file name',
}

export class GeneratorFormatNotValidError extends Error {
config: Dictionary<string>
Expand All @@ -14,9 +29,9 @@ export class GeneratorPathNotExists extends Error {}

export const handleGenerateError = (e: Error) => {
if (e instanceof GeneratorFormatNotValidError) {
const options = Object.keys(PrismaClassGeneratorOptions).map((key) => {
const value = PrismaClassGeneratorOptions[key]
return `\t${key} = (${value.defaultValue}) <- [${value.desc}]`
const options = Object.keys(DEFAULT_OPTIONS).map((key) => {
const value = DEFAULT_OPTIONS[key]
return `\t${key} = (${value}) <-- ${OPTIONS_DESCRIPTION[key]}`
})
log(
[
Expand Down
71 changes: 26 additions & 45 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PrismaConvertor } from './convertor'
import {
getRelativeTSPath,
parseBoolean,
parseNameConvention,
parseNumber,
prettierFormat,
writeTSFile,
Expand All @@ -13,53 +14,25 @@ import { INDEX_TEMPLATE } from './templates/index.template'
import { ImportComponent } from './components/import.component'
import * as prettier from 'prettier'
import { FileComponent } from './components/file.component'
import { PrismaClassGeneratorOptions } from './interfaces/options'

export const GENERATOR_NAME = 'Prisma Class Generator'

export const PrismaClassGeneratorOptions = {
makeIndexFile: {
desc: 'make index file',
defaultValue: true,
},
dryRun: {
desc: 'dry run',
defaultValue: true,
},
separateRelationFields: {
desc: 'separate relation fields',
defaultValue: false,
},
useSwagger: {
desc: 'use swagger decorstor',
defaultValue: true,
},
useGraphQL: {
desc: 'use graphql',
defaultValue: false,
},
useUndefinedDefault: {
desc: 'use undefined default',
defaultValue: false,
},
clientImportPath: {
desc: 'set prisma import path instead @prisma/client',
defaultValue: undefined,
},
useNonNullableAssertions: {
desc: 'applies non-nullable assertions (!) to class properties',
defaultValue: false,
},
preserveDefaultNullable: {
defaultValue: false,
desc: 'preserve default nullable behavior',
},
export const DEFAULT_OPTIONS: PrismaClassGeneratorOptions = {
makeIndexFile: true,
dryRun: true,
separateRelationFields: false,
useSwagger: true,
useGraphQL: false,
useUndefinedDefault: false,
clientImportPath: undefined,
useNonNullableAssertions: false,
preserveDefaultNullable: false,
nameConvention: 'snake',
} as const

export type PrismaClassGeneratorOptionsKeys =
keyof typeof PrismaClassGeneratorOptions
export type PrismaClassGeneratorConfig = Partial<
Record<PrismaClassGeneratorOptionsKeys, any>
>
export type PrismaClassGeneratorOptionsKeys = keyof PrismaClassGeneratorOptions
export type PrismaClassGeneratorConfig = Partial<PrismaClassGeneratorOptions>

export class PrismaClassGenerator {
static instance: PrismaClassGenerator
Expand Down Expand Up @@ -139,7 +112,12 @@ export class PrismaClassGenerator {

const classes = convertor.getClasses()
const files = classes.map(
(classComponent) => new FileComponent({ classComponent, output }),
(classComponent) =>
new FileComponent({
classComponent,
output,
case: config.nameConvention,
}),
)

const classToPath = files.reduce((result, fileRow) => {
Expand Down Expand Up @@ -198,12 +176,15 @@ export class PrismaClassGenerator {
const config = this.options.generator.config

const result: PrismaClassGeneratorConfig = {}
for (const optionName in PrismaClassGeneratorOptions) {
const { defaultValue } = PrismaClassGeneratorOptions[optionName]
for (const optionName in DEFAULT_OPTIONS) {
const defaultValue = DEFAULT_OPTIONS[optionName]
result[optionName] = defaultValue

const value = config[optionName]
if (value) {
if (optionName === 'nameConvention') {
result[optionName] = parseNameConvention(value)
}
if (typeof defaultValue === 'boolean') {
result[optionName] = parseBoolean(value)
} else if (typeof defaultValue === 'number') {
Expand Down
62 changes: 62 additions & 0 deletions src/interfaces/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export interface PrismaClassGeneratorOptions {
/**
* @description make index file
* @default true
* @type boolean
*/
makeIndexFile: boolean
/**
* @description dry run
* @default true
* @type boolean
*/
dryRun: boolean
/**
* @description separate relation fields
* @default false
* @type boolean
*/
separateRelationFields: boolean
/**
* @description use swagger decorstor
* @default true
* @type boolean
*/
useSwagger: boolean
/**
* @description use graphql
* @default false
* @type boolean
*/
useGraphQL: boolean
/**
* @description use undefined default
* @default false
* @type boolean
*/
useUndefinedDefault: boolean
/**
* @description set prisma import path instead `@prisma/client`
* @default undefined
* @type string | undefined
*/
clientImportPath: string | undefined
/**
* @description applies non-nullable assertions (!) to class properties
* @default false
* @type boolean
*/
useNonNullableAssertions: boolean
/**
* @default false
* @description preserve default nullable behavior
* @type boolean
*/
preserveDefaultNullable: boolean
/**
* @description name convention for generated classes file name
* @default 'snake'
* @type 'snake' | 'camel' | 'pascal' | 'kebab'
*/
nameConvention: 'snake' | 'camel' | 'pascal' | 'kebab'
}
17 changes: 17 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { GENERATOR_NAME } from './generator'
import { GeneratorFormatNotValidError } from './error-handler'
import { DMMF } from '@prisma/generator-helper'
import { Options, format } from 'prettier'
import { PrismaClassGeneratorOptions } from './interfaces/options'

export const capitalizeFirst = (src: string) => {
return src.charAt(0).toUpperCase() + src.slice(1)
Expand Down Expand Up @@ -65,6 +66,22 @@ export const parseNumber = (value: unknown): number => {
return numbered
}

export const parseNameConvention = (
value: string | string[],
): PrismaClassGeneratorOptions['nameConvention'] => {
if (Array.isArray(value)) {
throw new GeneratorFormatNotValidError(
`parseNameConvention failed : "nameConvention" should be string type`,
)
}
if (['snake', 'camel', 'pascal', 'kebab'].includes(value) === false) {
throw new GeneratorFormatNotValidError(
`parseNameConvention failed : "${value}" is not valid name convention ( snake | camel | pascal | kebab )`,
)
}
return value as PrismaClassGeneratorOptions['nameConvention']
}

export const toArray = <T>(value: T | T[]): T[] => {
return Array.isArray(value) ? value : [value]
}
Expand Down