prisma-generator-express 1.19.0 → 1.20.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/src/index.ts ADDED
@@ -0,0 +1,100 @@
1
+ import {
2
+ generatorHandler,
3
+ GeneratorOptions,
4
+ DMMF,
5
+ } from '@prisma/generator-helper'
6
+ import { generateUnifiedHandler } from './generators/generateUnifiedHandler'
7
+ import { generateRouterFunction } from './generators/generateRouter'
8
+ import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI'
9
+ import { generateUnifiedDocs } from './generators/generateUnifiedDocs'
10
+ import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper'
11
+ import {
12
+ generateImportPrismaStatement,
13
+ getRelativeClientPath,
14
+ } from './generators/generateImportPrismaStatement'
15
+ import { writeFileSafely } from './utils/writeFileSafely'
16
+ import { copyFiles } from './utils/copyFiles'
17
+ import { GENERATOR_NAME } from './constants'
18
+
19
+ generatorHandler({
20
+ onManifest() {
21
+ return {
22
+ version: require('../package.json').version,
23
+ defaultOutput: '../generated',
24
+ prettyName: GENERATOR_NAME,
25
+ }
26
+ },
27
+
28
+ async onGenerate(options: GeneratorOptions) {
29
+ const prismaImportStatement = generateImportPrismaStatement(options)
30
+
31
+ console.log('\n═══ Prisma Generator Express ═══')
32
+
33
+ await copyFiles(options)
34
+
35
+ const modelNames: string[] = []
36
+
37
+ for (const model of options.dmmf.datamodel.models) {
38
+ if (
39
+ model.documentation &&
40
+ model.documentation.includes('generator off')
41
+ ) {
42
+ console.log(` Skipping: ${model.name} (generator off)`)
43
+ continue
44
+ }
45
+
46
+ modelNames.push(model.name)
47
+
48
+ const relativeClientPath = getRelativeClientPath(options, model.name)
49
+
50
+ await writeFileSafely({
51
+ content: generateUnifiedHandler({
52
+ model: model as DMMF.Model,
53
+ prismaImportStatement,
54
+ }),
55
+ options,
56
+ model: model as DMMF.Model,
57
+ operation: 'Handlers',
58
+ })
59
+
60
+ await writeFileSafely({
61
+ content: generateRouterFunction({
62
+ model: model as DMMF.Model,
63
+ enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
64
+ relativeClientPath,
65
+ }),
66
+ options,
67
+ model: model as DMMF.Model,
68
+ operation: 'Router',
69
+ })
70
+
71
+ await writeFileSafely({
72
+ content: generateScalarUIHandler({
73
+ model: model as DMMF.Model,
74
+ enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
75
+ }),
76
+ options,
77
+ model: model as DMMF.Model,
78
+ operation: 'Docs',
79
+ })
80
+ }
81
+
82
+ await writeFileSafely({
83
+ content: generateUnifiedDocs(modelNames),
84
+ options,
85
+ operation: 'combinedDocs',
86
+ })
87
+
88
+ await writeFileSafely({
89
+ content: generateQueryBuilderHelper(options),
90
+ options,
91
+ operation: 'queryBuilder',
92
+ })
93
+
94
+ console.log('\n═══ Generation Complete ═══')
95
+ console.log(`✓ ${modelNames.length} models`)
96
+ console.log(`✓ OpenAPI documentation generated`)
97
+ console.log(`✓ Query builder helper generated`)
98
+ console.log('')
99
+ },
100
+ })
@@ -0,0 +1,134 @@
1
+ import { GeneratorOptions } from '@prisma/generator-helper'
2
+ import * as fs from 'fs'
3
+ import * as path from 'path'
4
+
5
+ export interface CopyFilesConfig {
6
+ includeCacheUtils?: boolean
7
+ includeValidatorUtils?: boolean
8
+ }
9
+
10
+ export async function copyFiles(
11
+ options: GeneratorOptions,
12
+ config?: CopyFilesConfig,
13
+ ): Promise<void> {
14
+ const copyConfig = config || {}
15
+ const outputPath = options.generator.output?.value
16
+ if (!outputPath) return
17
+
18
+ const srcDir = path.join(__dirname, '..', '..', 'src', 'copy')
19
+
20
+ const baseFiles = [
21
+ 'routeConfig.ts',
22
+ 'parseQueryParams.ts',
23
+ 'buildModelOpenApi.ts',
24
+ 'operationDefinitions.ts',
25
+ 'misc.ts',
26
+ ]
27
+
28
+ let allCopied = true
29
+
30
+ console.log(`Copying utility files to: ${outputPath}`)
31
+
32
+ for (const file of baseFiles) {
33
+ const ok = await copyFile(srcDir, outputPath, file, { required: true })
34
+ if (!ok) allCopied = false
35
+ }
36
+
37
+ if (copyConfig.includeCacheUtils) {
38
+ const ok = await copyFile(srcDir, outputPath, 'cacheManager.ts', {
39
+ required: true,
40
+ })
41
+ if (!ok) allCopied = false
42
+ }
43
+
44
+ if (copyConfig.includeValidatorUtils) {
45
+ const ok = await copyFile(srcDir, outputPath, 'inputValidator.ts', {
46
+ required: true,
47
+ })
48
+ if (!ok) allCopied = false
49
+ }
50
+
51
+ const clientDir = path.join(outputPath, 'client')
52
+ if (!fs.existsSync(clientDir)) {
53
+ fs.mkdirSync(clientDir, { recursive: true })
54
+ }
55
+
56
+ const clientSrcDir = path.join(__dirname, '..', '..', 'src', 'client')
57
+ const clientOk = await copyFile(
58
+ clientSrcDir,
59
+ clientDir,
60
+ 'encodeQueryParams.ts',
61
+ {
62
+ required: true,
63
+ importRewrites: [{ from: '../copy/misc', to: '../misc' }],
64
+ },
65
+ )
66
+ if (!clientOk) allCopied = false
67
+
68
+ if (allCopied) {
69
+ console.log(`✓ Utility files copied successfully`)
70
+ } else {
71
+ throw new Error(
72
+ 'One or more required utility files could not be copied. Generation aborted.',
73
+ )
74
+ }
75
+ }
76
+
77
+ interface CopyFileOptions {
78
+ required?: boolean
79
+ importRewrites?: Array<{ from: string; to: string }>
80
+ }
81
+
82
+ async function copyFile(
83
+ srcDir: string,
84
+ destDir: string,
85
+ filename: string,
86
+ options?: CopyFileOptions,
87
+ ): Promise<boolean> {
88
+ const srcPath = path.join(srcDir, filename)
89
+ const destPath = path.join(destDir, filename)
90
+
91
+ if (!fs.existsSync(srcPath)) {
92
+ if (options?.required) {
93
+ console.error(`✗ Required source file not found: ${srcPath}`)
94
+ return false
95
+ }
96
+ console.warn(`⚠️ Source file not found: ${srcPath}`)
97
+ return true
98
+ }
99
+
100
+ const destDirPath = path.dirname(destPath)
101
+ if (!fs.existsSync(destDirPath)) {
102
+ fs.mkdirSync(destDirPath, { recursive: true })
103
+ }
104
+
105
+ let content = fs.readFileSync(srcPath, 'utf-8')
106
+
107
+ if (options?.importRewrites) {
108
+ for (const rewrite of options.importRewrites) {
109
+ content = content.replace(
110
+ new RegExp(`from ['"]${escapeRegex(rewrite.from)}['"]`, 'g'),
111
+ `from '${rewrite.to}'`,
112
+ )
113
+ }
114
+ }
115
+
116
+ const header = `/**
117
+ * Auto-generated by prisma-generator-express
118
+ * DO NOT EDIT MANUALLY
119
+ */
120
+
121
+ `
122
+
123
+ if (!content.startsWith('/**')) {
124
+ content = header + content
125
+ }
126
+
127
+ fs.writeFileSync(destPath, content)
128
+ console.log(` ✓ Copied: ${filename}`)
129
+ return true
130
+ }
131
+
132
+ function escapeRegex(str: string): string {
133
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
134
+ }
@@ -0,0 +1,7 @@
1
+ export const capitalize = (str: string) =>
2
+ str.charAt(0).toUpperCase() + str.slice(1)
3
+
4
+ export function toPascalCase(str: string) {
5
+ if (!str) return str
6
+ return str.charAt(0).toLowerCase() + str.slice(1)
7
+ }
@@ -0,0 +1,83 @@
1
+ import { GeneratorOptions } from '@prisma/generator-helper'
2
+ import { DMMF } from '@prisma/generator-helper'
3
+ import * as fs from 'fs'
4
+ import * as path from 'path'
5
+ import prettier from 'prettier'
6
+
7
+ interface WriteFileOptions {
8
+ content: string
9
+ options: GeneratorOptions
10
+ model?: DMMF.Model
11
+ operation: string
12
+ }
13
+
14
+ let _prettierOptions: prettier.Options | null | undefined
15
+
16
+ async function getPrettierOptions(): Promise<prettier.Options | null> {
17
+ if (_prettierOptions !== undefined) return _prettierOptions
18
+ _prettierOptions = await prettier.resolveConfig(process.cwd())
19
+ return _prettierOptions
20
+ }
21
+
22
+ export async function writeFileSafely({
23
+ content,
24
+ options,
25
+ model,
26
+ operation,
27
+ }: WriteFileOptions): Promise<void> {
28
+ const outputPath = options.generator.output?.value
29
+ if (!outputPath) {
30
+ throw new Error('Output path not defined')
31
+ }
32
+
33
+ let filePath: string
34
+
35
+ switch (operation) {
36
+ case 'cacheConfig':
37
+ filePath = path.join(outputPath, 'cacheConfig.ts')
38
+ break
39
+
40
+ case 'types/inputs':
41
+ filePath = path.join(outputPath, 'types', 'inputs.ts')
42
+ break
43
+
44
+ case 'combinedDocs':
45
+ filePath = path.join(outputPath, 'combinedDocs.ts')
46
+ break
47
+
48
+ case 'queryBuilder':
49
+ filePath = path.join(outputPath, 'queryBuilder.ts')
50
+ break
51
+
52
+ default:
53
+ if (!model) {
54
+ throw new Error(`Model required for operation: ${operation}`)
55
+ }
56
+ filePath = path.join(
57
+ outputPath,
58
+ model.name,
59
+ `${model.name}${operation}.ts`,
60
+ )
61
+ }
62
+
63
+ const dirPath = path.dirname(filePath)
64
+ if (!fs.existsSync(dirPath)) {
65
+ fs.mkdirSync(dirPath, { recursive: true })
66
+ }
67
+
68
+ let formattedContent: string
69
+ try {
70
+ const resolvedOptions = await getPrettierOptions()
71
+ formattedContent = await prettier.format(content, {
72
+ ...resolvedOptions,
73
+ parser: 'typescript',
74
+ })
75
+ } catch (error) {
76
+ console.warn(
77
+ `⚠️ Prettier formatting failed for ${path.basename(filePath)}, writing unformatted`,
78
+ )
79
+ formattedContent = content
80
+ }
81
+
82
+ fs.writeFileSync(filePath, formattedContent)
83
+ }