@nestledjs/api 0.0.1
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 +5 -0
- package/README.md +11 -0
- package/eslint.config.cjs +28 -0
- package/generators.json +69 -0
- package/package.json +21 -0
- package/project.json +47 -0
- package/src/account/files/data-access/src/index.ts__tmpl__ +5 -0
- package/src/account/files/data-access/src/lib/api-account-data-access.module.ts__tmpl__ +10 -0
- package/src/account/files/data-access/src/lib/api-account-data-access.service.ts__tmpl__ +152 -0
- package/src/account/files/data-access/src/lib/dto/account-create-email.input.ts__tmpl__ +9 -0
- package/src/account/files/data-access/src/lib/dto/account-update-password.input.ts__tmpl__ +16 -0
- package/src/account/files/data-access/src/lib/dto/account-update-profile.input.ts__tmpl__ +25 -0
- package/src/account/files/feature/src/index.ts__tmpl__ +1 -0
- package/src/account/files/feature/src/lib/api-account-feature.module.ts__tmpl__ +9 -0
- package/src/account/files/feature/src/lib/api-account-feature.resolver.ts__tmpl__ +83 -0
- package/src/account/generator.spec.ts +71 -0
- package/src/account/generator.ts +20 -0
- package/src/account/schema.d.ts +3 -0
- package/src/account/schema.json +13 -0
- package/src/app/files/src/app.config.ts__tmpl__ +66 -0
- package/src/app/files/src/app.module.ts__tmpl__ +43 -0
- package/src/app/files/src/applogger.middleware.ts__tmpl__ +21 -0
- package/src/app/files/src/main.ts__tmpl__ +33 -0
- package/src/app/files/webpack.config.js__tmpl__ +54 -0
- package/src/app/generator.spec.ts +112 -0
- package/src/app/generator.ts +105 -0
- package/src/app/schema.d.ts +1 -0
- package/src/app/schema.json +9 -0
- package/src/config/files/src/index.ts__tmpl__ +3 -0
- package/src/config/files/src/lib/config.service.ts__tmpl__ +51 -0
- package/src/config/files/src/lib/configuration.ts__tmpl__ +32 -0
- package/src/config/files/src/lib/validation.ts__tmpl__ +21 -0
- package/src/config/generator.spec.ts +47 -0
- package/src/config/generator.ts +16 -0
- package/src/config/schema.d.ts +3 -0
- package/src/config/schema.json +13 -0
- package/src/core/files/data-access/src/index.ts__tmpl__ +5 -0
- package/src/core/files/data-access/src/lib/api-core-data-access.module.ts__tmpl__ +9 -0
- package/src/core/files/data-access/src/lib/api-core-data-access.service.ts__tmpl__ +97 -0
- package/src/core/files/data-access/src/lib/api-core-pub-sub.ts__tmpl__ +37 -0
- package/src/core/files/data-access/src/lib/dto/core-paging.input.ts__tmpl__ +26 -0
- package/src/core/files/data-access/src/lib/dto/multi-select-input.ts__tmpl__ +7 -0
- package/src/core/files/data-access/src/lib/models/core-paging.ts__tmpl__ +19 -0
- package/src/core/files/feature/src/index.ts__tmpl__ +2 -0
- package/src/core/files/feature/src/lib/api-core-feature.controller.ts__tmpl__ +12 -0
- package/src/core/files/feature/src/lib/api-core-feature.module.ts__tmpl__ +86 -0
- package/src/core/files/feature/src/lib/api-core-feature.resolver.ts__tmpl__ +12 -0
- package/src/core/files/feature/src/lib/api-core-feature.service.ts__tmpl__ +55 -0
- package/src/core/files/feature/src/lib/config/configuration.ts__tmpl__ +32 -0
- package/src/core/files/feature/src/lib/config/validation.ts__tmpl__ +25 -0
- package/src/core/files/feature/src/lib/plugins/complexity.plugin.ts__tmpl__ +51 -0
- package/src/core/files/feature/src/lib/plugins/logging.plugin.ts__tmpl__ +17 -0
- package/src/core/files/models/src/index.ts__tmpl__ +1 -0
- package/src/core/files/models/src/lib/generate-models.ts__tmpl__ +294 -0
- package/src/core/files/models/src/lib/models/core-paging.model.ts__tmpl__ +25 -0
- package/src/core/generator.spec.ts +85 -0
- package/src/core/generator.ts +35 -0
- package/src/core/schema.d.ts +3 -0
- package/src/core/schema.json +13 -0
- package/src/custom/generator.spec.ts +75 -0
- package/src/custom/generator.ts +239 -0
- package/src/custom/schema.json +21 -0
- package/src/custom/schema.ts +5 -0
- package/src/extended/generator.spec.ts +95 -0
- package/src/extended/generator.ts +161 -0
- package/src/extended/index.ts +1 -0
- package/src/extended/schema.json +12 -0
- package/src/extended/schema.ts +3 -0
- package/src/generate-crud/files/data-access/src/index.ts__tmpl__ +3 -0
- package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.module.ts__tmpl__ +11 -0
- package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.service.ts__tmpl__ +72 -0
- package/src/generate-crud/files/data-access/src/lib/dto/index.ts__tmpl__ +224 -0
- package/src/generate-crud/files/feature/.gitkeep +0 -0
- package/src/generate-crud/generator.spec.ts +84 -0
- package/src/generate-crud/generator.ts +354 -0
- package/src/generate-crud/schema.json +32 -0
- package/src/generate-crud/schema.ts +8 -0
- package/src/index.ts +13 -0
- package/src/plugin/generator.spec.ts +18 -0
- package/src/plugin/generator.ts +74 -0
- package/src/plugin/schema.json +14 -0
- package/src/plugin/schema.ts +4 -0
- package/src/prisma/files/src/index.ts__tmpl__ +1 -0
- package/src/prisma/files/src/lib/.gitkeep +1 -0
- package/src/prisma/files/src/lib/schemas/schema.prisma__tmpl__ +402 -0
- package/src/prisma/files/src/lib/seed/seed-data/iso-3166-countries.ts__tmpl__ +3239 -0
- package/src/prisma/files/src/lib/seed/seed-data/seed-users.ts__tmpl__ +32 -0
- package/src/prisma/files/src/lib/seed/seed.ts__tmpl__ +64 -0
- package/src/prisma/generator.spec.ts +60 -0
- package/src/prisma/generator.ts +61 -0
- package/src/prisma/schema.d.ts +3 -0
- package/src/prisma/schema.json +13 -0
- package/src/setup/generator.md +49 -0
- package/src/setup/generator.spec.ts +18 -0
- package/src/setup/generator.ts +106 -0
- package/src/setup/schema.json +8 -0
- package/src/smtp-mailer/files/data-access/src/index.ts__tmpl__ +2 -0
- package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.module.ts__tmpl__ +10 -0
- package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.service.ts__tmpl__ +61 -0
- package/src/smtp-mailer/generator.spec.ts +41 -0
- package/src/smtp-mailer/generator.ts +14 -0
- package/src/smtp-mailer/schema.d.ts +0 -0
- package/src/smtp-mailer/schema.json +7 -0
- package/src/user/files/data-access/src/index.ts__tmpl__ +5 -0
- package/src/user/files/data-access/src/lib/api-user-data-access.module.ts__tmpl__ +10 -0
- package/src/user/files/data-access/src/lib/api-user-data-access.service.ts__tmpl__ +119 -0
- package/src/user/files/data-access/src/lib/dto/admin-create-user.input.ts__tmpl__ +20 -0
- package/src/user/files/data-access/src/lib/dto/admin-update-user.input.ts__tmpl__ +29 -0
- package/src/user/files/feature/src/index.ts__tmpl__ +1 -0
- package/src/user/files/feature/src/lib/api-user-feature-admin.resolver.ts__tmpl__ +57 -0
- package/src/user/files/feature/src/lib/api-user-feature.module.ts__tmpl__ +10 -0
- package/src/user/files/feature/src/lib/api-user-feature.resolver.ts__tmpl__ +17 -0
- package/src/user/generator.spec.ts +41 -0
- package/src/user/generator.ts +15 -0
- package/src/user/schema.d.ts +0 -0
- package/src/user/schema.json +7 -0
- package/src/utils/files/src/index.ts__tmpl__ +3 -0
- package/src/utils/files/src/lib/decorators/ctx-user.decorator.ts__tmpl__ +6 -0
- package/src/utils/files/src/lib/guards/gql-auth-admin.guard.ts__tmpl__ +39 -0
- package/src/utils/files/src/lib/guards/gql-auth.guard.ts__tmpl__ +11 -0
- package/src/utils/generator.ts +14 -0
- package/src/utils/schema.json +8 -0
- package/src/workspace-setup/generator.md +39 -0
- package/src/workspace-setup/generator.spec.ts +82 -0
- package/src/workspace-setup/generator.ts +49 -0
- package/src/workspace-setup/lib/helpers.ts +142 -0
- package/src/workspace-setup/schema.d.ts +3 -0
- package/src/workspace-setup/schema.json +7 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +23 -0
- package/tsconfig.spec.json +22 -0
- package/vite.config.mts +37 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs' // Added readdirSync, lstatSync
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { execSync } from 'child_process'
|
|
4
|
+
import { getDMMF } from '@prisma/internals'
|
|
5
|
+
|
|
6
|
+
// Find project root and get schema path from package.json
|
|
7
|
+
function findProjectRoot(startDir: string): string {
|
|
8
|
+
try {
|
|
9
|
+
// Try to use Nx's utility first if available
|
|
10
|
+
try {
|
|
11
|
+
const { findRootSync } = require('@nx/devkit')
|
|
12
|
+
return findRootSync(startDir)
|
|
13
|
+
} catch {
|
|
14
|
+
// Fallback to process.cwd() which should be project root when running scripts
|
|
15
|
+
return process.cwd()
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Error finding project root:', error)
|
|
19
|
+
return process.cwd() // Fallback
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Determine the correct prisma import path based on package.json configuration
|
|
24
|
+
function getPrismaImportPath(): string {
|
|
25
|
+
try {
|
|
26
|
+
const projectRoot = findProjectRoot(__dirname)
|
|
27
|
+
const packageJsonPath = join(projectRoot, 'package.json')
|
|
28
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
|
29
|
+
|
|
30
|
+
const schemaPathSetting = packageJson.prisma?.schema || ''
|
|
31
|
+
|
|
32
|
+
// Updated logic: if the schema path includes 'libs/api/prisma' (or similar new structure)
|
|
33
|
+
if (schemaPathSetting.includes('libs/api/prisma') || schemaPathSetting.includes('prisma/src/lib')) {
|
|
34
|
+
return '@<%= npmScope %>/api/prisma'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fallback to the original path if not explicitly the new structure
|
|
38
|
+
return '@<%= npmScope %>/api/core/data-access'
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error determining Prisma import path:', error)
|
|
41
|
+
// Default to the new path as a sensible fallback
|
|
42
|
+
return '@<%= npmScope %>/api/prisma'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get schema directory path from package.json
|
|
47
|
+
function getPrismaSchemaDir(): string {
|
|
48
|
+
// Renamed
|
|
49
|
+
try {
|
|
50
|
+
const projectRoot = findProjectRoot(__dirname)
|
|
51
|
+
const packageJsonPath = join(projectRoot, 'package.json')
|
|
52
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
|
53
|
+
|
|
54
|
+
if (!packageJson.prisma?.schema) {
|
|
55
|
+
throw new Error('prisma.schema path not found in package.json')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Join the project root with the schema path (which should be a directory)
|
|
59
|
+
return join(projectRoot, packageJson.prisma.schema)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Error getting Prisma schema directory path:', error)
|
|
62
|
+
// Fallback to the old directory path for backward compatibility
|
|
63
|
+
return join(__dirname, '../../../data-access/src/prisma/schemas') // Assuming this was the directory
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Paths
|
|
68
|
+
const schemaDir = getPrismaSchemaDir() // Renamed variable
|
|
69
|
+
const outputDir = join(__dirname, './models')
|
|
70
|
+
const prismaImportPath = getPrismaImportPath()
|
|
71
|
+
|
|
72
|
+
async function main() {
|
|
73
|
+
// Ensure output directory exists
|
|
74
|
+
if (!existsSync(outputDir)) {
|
|
75
|
+
mkdirSync(outputDir, { recursive: true })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Verify schema directory exists and is a directory
|
|
79
|
+
if (!existsSync(schemaDir) || !lstatSync(schemaDir).isDirectory()) {
|
|
80
|
+
console.error(`Error: Prisma schema directory not found or is not a directory at ${schemaDir}`)
|
|
81
|
+
console.error('Please check your package.json prisma.schema configuration.')
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Generate Prisma client first (Prisma CLI handles directory schemas well)
|
|
86
|
+
console.log(`Generating Prisma client using schema files in ${schemaDir}...`)
|
|
87
|
+
// Note: Prisma CLI will look for a schema.prisma file in this dir,
|
|
88
|
+
// or process all .prisma files if it's configured for multi-file.
|
|
89
|
+
// For `prisma generate`, it's often best if there's a primary schema.prisma that imports others,
|
|
90
|
+
// or if all files are at the same level.
|
|
91
|
+
// If `prisma generate` itself fails, you might need to adjust how Prisma is set up for multi-file schemas.
|
|
92
|
+
// For now, we assume `prisma generate` works with the directory.
|
|
93
|
+
execSync(`npx prisma generate --schema "${schemaDir}"`, { stdio: 'inherit' })
|
|
94
|
+
// Alternatively, if your Prisma setup truly supports just passing the directory:
|
|
95
|
+
// execSync(`npx prisma generate --schema "${schemaDir}"`, { stdio: 'inherit' });
|
|
96
|
+
// Pick the one that works for your `prisma generate` setup. If you have a main `schema.prisma` that
|
|
97
|
+
// is just configuration and other files hold models/enums, pointing `prisma generate` to that
|
|
98
|
+
// `schema.prisma` specifically might be more robust, as it will then find the other files
|
|
99
|
+
// based on its own internal logic (like `previewFeatures = ["multiFileSchema"]`).
|
|
100
|
+
// For getDMMF, we will manually concatenate.
|
|
101
|
+
|
|
102
|
+
// Read and parse all schema files for DMMF
|
|
103
|
+
console.log('Reading Prisma schema files for DMMF generation...')
|
|
104
|
+
const schemaFiles = readdirSync(schemaDir).filter((file) => file.endsWith('.prisma'))
|
|
105
|
+
|
|
106
|
+
if (schemaFiles.length === 0) {
|
|
107
|
+
console.error(`Error: No .prisma files found in directory ${schemaDir}`)
|
|
108
|
+
process.exit(1)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let combinedSchemaContent = ''
|
|
112
|
+
console.log('Concatenating schema files:')
|
|
113
|
+
for (const file of schemaFiles) {
|
|
114
|
+
const filePath = join(schemaDir, file)
|
|
115
|
+
console.log(` - ${filePath}`)
|
|
116
|
+
combinedSchemaContent += readFileSync(filePath, 'utf-8') + '\n\n' // Add newlines between files
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const dmmf = await getDMMF({ datamodel: combinedSchemaContent })
|
|
120
|
+
|
|
121
|
+
// Extract models and enums from parsed schema
|
|
122
|
+
const models = dmmf.datamodel.models
|
|
123
|
+
const enums = dmmf.datamodel.enums
|
|
124
|
+
|
|
125
|
+
// Generate models
|
|
126
|
+
console.log('Generating TypeScript models...')
|
|
127
|
+
const modelsOutput = generateModels(models, enums)
|
|
128
|
+
writeFileSync(join(outputDir, 'models.ts'), modelsOutput)
|
|
129
|
+
|
|
130
|
+
// Generate enums
|
|
131
|
+
console.log('Generating TypeScript enums...')
|
|
132
|
+
const enumsOutput = generateEnums(enums)
|
|
133
|
+
writeFileSync(join(outputDir, 'enums.ts'), enumsOutput)
|
|
134
|
+
|
|
135
|
+
// Generate index file
|
|
136
|
+
console.log('Generating index file...')
|
|
137
|
+
const indexOutput = generateIndex()
|
|
138
|
+
writeFileSync(join(outputDir, 'index.ts'), indexOutput)
|
|
139
|
+
|
|
140
|
+
console.log('Models and enums generated successfully!')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function generateModels(models: readonly any[], enums: readonly any[]): string {
|
|
144
|
+
// Check if any model field uses Float
|
|
145
|
+
const usesFloat = models.some(model => model.fields.some((field: { type: string }) => field.type === 'Float'));
|
|
146
|
+
let output = `import { Field, ObjectType${usesFloat ? ', Float' : ''}, Int } from '@nestjs/graphql';\n`
|
|
147
|
+
output += `import { GraphQLJSONObject } from 'graphql-type-json';\n`
|
|
148
|
+
|
|
149
|
+
// Check if any model field uses Decimal
|
|
150
|
+
const usesDecimal = models.some(model => model.fields.some((field: { type: string }) => field.type === 'Decimal'));
|
|
151
|
+
if (usesDecimal) {
|
|
152
|
+
output += `import { Decimal } from '@prisma/client/runtime/library';\n`;
|
|
153
|
+
output += `import { GraphQLDecimal } from 'prisma-graphql-type-decimal';\n`;
|
|
154
|
+
}
|
|
155
|
+
// Ensure Prisma and enums are imported correctly
|
|
156
|
+
const enumNames = enums.map((e) => e.name)
|
|
157
|
+
output += `import { Prisma } from '${prismaImportPath}';\n`
|
|
158
|
+
if (enumNames.length > 0) {
|
|
159
|
+
output += `import { ${enumNames.join(', ')} } from './enums';\n`;
|
|
160
|
+
}
|
|
161
|
+
output += `\n`
|
|
162
|
+
|
|
163
|
+
for (const model of models) {
|
|
164
|
+
output += `@ObjectType({ description: undefined })\nexport class ${model.name} {\n` // Added description: undefined for clarity if not set
|
|
165
|
+
|
|
166
|
+
for (const field of model.fields) {
|
|
167
|
+
const isList = field.isList
|
|
168
|
+
const isEnum = field.kind === 'enum'
|
|
169
|
+
const isRelation = field.kind === 'object'
|
|
170
|
+
const originalType = field.type
|
|
171
|
+
// const isRequired = field.isRequired // Natively supported by DMMF
|
|
172
|
+
|
|
173
|
+
// DEBUG LINE
|
|
174
|
+
// console.log(`DEBUG: Processing field: ${model.name}.${field.name}, Kind: ${field.kind}, OriginalType: ${originalType}, IsList: ${isList}, IsRequired: ${isRequired}`);
|
|
175
|
+
|
|
176
|
+
let tsType = originalType
|
|
177
|
+
|
|
178
|
+
if (field.kind === 'scalar') {
|
|
179
|
+
if (originalType === 'Int' || originalType === 'Float') {
|
|
180
|
+
tsType = 'number'
|
|
181
|
+
} else if (originalType === 'Decimal') {
|
|
182
|
+
tsType = 'Decimal'
|
|
183
|
+
} else if (originalType === 'String' || originalType === 'ID') {
|
|
184
|
+
tsType = 'string'
|
|
185
|
+
} else if (originalType === 'Boolean') {
|
|
186
|
+
tsType = 'boolean'
|
|
187
|
+
} else if (originalType === 'DateTime') {
|
|
188
|
+
tsType = 'Date'
|
|
189
|
+
} else if (originalType === 'Json') {
|
|
190
|
+
tsType = 'Prisma.JsonValue'
|
|
191
|
+
} else if (originalType === 'BigInt') {
|
|
192
|
+
tsType = 'bigint' // Prisma uses bigint for BigInt
|
|
193
|
+
} else if (originalType === 'Bytes') {
|
|
194
|
+
tsType = 'Buffer' // Prisma uses Buffer for Bytes
|
|
195
|
+
}
|
|
196
|
+
} else if (field.kind === 'enum') {
|
|
197
|
+
tsType = originalType
|
|
198
|
+
} else if (field.kind === 'object') {
|
|
199
|
+
tsType = originalType
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let decorator = '@Field('
|
|
203
|
+
const options: string[] = []
|
|
204
|
+
// Always add nullable: true for all fields
|
|
205
|
+
options.push('nullable: true')
|
|
206
|
+
|
|
207
|
+
// Always declare the type in the decorator
|
|
208
|
+
let decoratorType = ''
|
|
209
|
+
if (isList) {
|
|
210
|
+
let listItemGraphQLType = originalType
|
|
211
|
+
if (originalType === 'Int') listItemGraphQLType = 'Int'
|
|
212
|
+
else if (originalType === 'Float') listItemGraphQLType = 'Float'
|
|
213
|
+
else if (originalType === 'Decimal') listItemGraphQLType = 'GraphQLDecimal'
|
|
214
|
+
else if (originalType === 'Json') listItemGraphQLType = 'GraphQLJSONObject'
|
|
215
|
+
else if (isEnum || isRelation)
|
|
216
|
+
listItemGraphQLType = originalType // Assumes enum/type is registered with GraphQL
|
|
217
|
+
else if (originalType === 'DateTime') listItemGraphQLType = 'Date'
|
|
218
|
+
else if (originalType === 'Boolean') listItemGraphQLType = 'Boolean'
|
|
219
|
+
else if (originalType.toLowerCase() === 'string' || originalType === 'ID') {
|
|
220
|
+
listItemGraphQLType = 'String'
|
|
221
|
+
} else {
|
|
222
|
+
listItemGraphQLType = originalType
|
|
223
|
+
}
|
|
224
|
+
decoratorType = `() => [${listItemGraphQLType}]`
|
|
225
|
+
} else {
|
|
226
|
+
// Always declare the type for non-list fields
|
|
227
|
+
if (originalType === 'Int') decoratorType = '() => Int'
|
|
228
|
+
else if (originalType === 'Float') decoratorType = '() => Float'
|
|
229
|
+
else if (originalType === 'Decimal') decoratorType = '() => GraphQLDecimal'
|
|
230
|
+
else if (originalType === 'Json') decoratorType = '() => GraphQLJSONObject'
|
|
231
|
+
else if (isEnum || isRelation) decoratorType = `() => ${originalType}`
|
|
232
|
+
else if (originalType === 'DateTime') decoratorType = '() => Date'
|
|
233
|
+
else if (originalType === 'Boolean') decoratorType = '() => Boolean'
|
|
234
|
+
else if (originalType.toLowerCase() === 'string' || originalType === 'ID') decoratorType = '() => String'
|
|
235
|
+
else decoratorType = `() => ${originalType}`
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
decorator += decoratorType
|
|
239
|
+
decorator += `, { ${options.join(', ')} }`
|
|
240
|
+
decorator += ')'
|
|
241
|
+
|
|
242
|
+
output += ` ${decorator}\n`
|
|
243
|
+
// Make all fields optional regardless of isRequired value
|
|
244
|
+
const optionalMarker = '?' // Always add a question mark
|
|
245
|
+
|
|
246
|
+
// For relations, use Partial<Type> only if it's not required and can be partially loaded
|
|
247
|
+
// Otherwise, if it's a relation, it's either the full Type or Type[] or null/undefined.
|
|
248
|
+
// For simplicity, let's assume if it's a relation object, it's the Type itself.
|
|
249
|
+
// Partial is useful for inputs or update DTOs, less so for GQL ObjectTypes unless specifically designed.
|
|
250
|
+
let finalTsType = tsType
|
|
251
|
+
if (isRelation && !isList) {
|
|
252
|
+
// Single related object
|
|
253
|
+
finalTsType = `${tsType}` // Not Partial<Type> by default for GQL object types
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Never add '| null' to any field
|
|
257
|
+
output += ` ${field.name}${optionalMarker}: ${finalTsType}${isList ? '[]' : ''} | null;\n\n`
|
|
258
|
+
}
|
|
259
|
+
output += `}\n\n`
|
|
260
|
+
}
|
|
261
|
+
return output
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function generateEnums(enums: readonly any[]): string {
|
|
265
|
+
let output = '// Generated from Prisma schema\n\n'
|
|
266
|
+
output += "import { registerEnumType } from '@nestjs/graphql';\n"
|
|
267
|
+
|
|
268
|
+
if (enums.length > 0) {
|
|
269
|
+
const enumNames = enums.map((e) => e.name).join(', ')
|
|
270
|
+
output += `import { ${enumNames} } from '${prismaImportPath}';\n\n`
|
|
271
|
+
output += `export { ${enumNames} };\n\n`
|
|
272
|
+
|
|
273
|
+
enums.forEach((enumType) => {
|
|
274
|
+
output += `registerEnumType(${enumType.name}, { name: '${enumType.name}' });\n\n`
|
|
275
|
+
})
|
|
276
|
+
} else {
|
|
277
|
+
output += '// No enums found in schema to generate.\n'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return output
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function generateIndex(): string {
|
|
284
|
+
return `// Generated from Prisma schema
|
|
285
|
+
export * from './models'
|
|
286
|
+
export * from './enums'
|
|
287
|
+
export * from './core-paging.model'
|
|
288
|
+
`
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
main().catch((e) => {
|
|
292
|
+
console.error('Error during generation:', e)
|
|
293
|
+
process.exit(1)
|
|
294
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Field, ObjectType } from '@nestjs/graphql'
|
|
2
|
+
|
|
3
|
+
@ObjectType()
|
|
4
|
+
export class CorePaging {
|
|
5
|
+
@Field({ nullable: true })
|
|
6
|
+
total?: number
|
|
7
|
+
|
|
8
|
+
@Field({ nullable: true })
|
|
9
|
+
count?: number
|
|
10
|
+
|
|
11
|
+
@Field({ nullable: true })
|
|
12
|
+
take?: number
|
|
13
|
+
|
|
14
|
+
@Field({ nullable: true })
|
|
15
|
+
page?: number
|
|
16
|
+
|
|
17
|
+
@Field({ nullable: true })
|
|
18
|
+
skip?: number
|
|
19
|
+
|
|
20
|
+
@Field({ nullable: true })
|
|
21
|
+
sum?: number
|
|
22
|
+
|
|
23
|
+
@Field({ nullable: true })
|
|
24
|
+
totalSum?: number
|
|
25
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { formatFiles, installPackagesTask, Tree } from '@nx/devkit'
|
|
2
|
+
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import { apiLibraryGenerator, installPlugins } from '@nestledjs/utils'
|
|
5
|
+
|
|
6
|
+
import generator from './generator'
|
|
7
|
+
|
|
8
|
+
vi.mock('@nestledjs/utils', async () => {
|
|
9
|
+
const actual = await vi.importActual('@nestledjs/utils')
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
apiLibraryGenerator: vi.fn(),
|
|
13
|
+
installPlugins: vi.fn(),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
vi.mock('@nx/devkit', async () => {
|
|
18
|
+
const actual = await vi.importActual('@nx/devkit')
|
|
19
|
+
return {
|
|
20
|
+
...actual,
|
|
21
|
+
formatFiles: vi.fn(),
|
|
22
|
+
installPackagesTask: vi.fn(),
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('core generator', () => {
|
|
27
|
+
let tree: Tree
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
tree = createTreeWithEmptyWorkspace()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should run successfully', async () => {
|
|
34
|
+
const callback = await generator(tree)
|
|
35
|
+
callback()
|
|
36
|
+
|
|
37
|
+
expect(installPlugins).toHaveBeenCalled()
|
|
38
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
39
|
+
tree,
|
|
40
|
+
{ name: 'core', overwrite: false },
|
|
41
|
+
expect.any(String),
|
|
42
|
+
'data-access',
|
|
43
|
+
)
|
|
44
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
45
|
+
tree,
|
|
46
|
+
{ name: 'core', overwrite: false },
|
|
47
|
+
expect.any(String),
|
|
48
|
+
'feature',
|
|
49
|
+
true,
|
|
50
|
+
)
|
|
51
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
52
|
+
tree,
|
|
53
|
+
{ name: 'core', overwrite: false },
|
|
54
|
+
expect.any(String),
|
|
55
|
+
'models',
|
|
56
|
+
)
|
|
57
|
+
expect(formatFiles).toHaveBeenCalledWith(tree)
|
|
58
|
+
expect(installPackagesTask).toHaveBeenCalledWith(tree)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should pass overwrite flag to apiLibraryGenerator when overwrite is true', async () => {
|
|
62
|
+
const callback = await generator(tree, { overwrite: true })
|
|
63
|
+
callback()
|
|
64
|
+
|
|
65
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
66
|
+
tree,
|
|
67
|
+
{ name: 'core', overwrite: true },
|
|
68
|
+
expect.any(String),
|
|
69
|
+
'data-access',
|
|
70
|
+
)
|
|
71
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
72
|
+
tree,
|
|
73
|
+
{ name: 'core', overwrite: true },
|
|
74
|
+
expect.any(String),
|
|
75
|
+
'feature',
|
|
76
|
+
true,
|
|
77
|
+
)
|
|
78
|
+
expect(apiLibraryGenerator).toHaveBeenCalledWith(
|
|
79
|
+
tree,
|
|
80
|
+
{ name: 'core', overwrite: true },
|
|
81
|
+
expect.any(String),
|
|
82
|
+
'models',
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { formatFiles, GeneratorCallback, installPackagesTask, joinPathFragments, Tree } from '@nx/devkit'
|
|
2
|
+
import { apiLibraryGenerator, installPlugins } from '@nestledjs/utils'
|
|
3
|
+
import { ApiCoreGeneratorSchema } from './schema'
|
|
4
|
+
|
|
5
|
+
export default async function generateLibraries(
|
|
6
|
+
tree: Tree,
|
|
7
|
+
options: ApiCoreGeneratorSchema = {},
|
|
8
|
+
): Promise<GeneratorCallback> {
|
|
9
|
+
const templateRootPath = joinPathFragments(__dirname, './files')
|
|
10
|
+
const overwrite = options.overwrite === true
|
|
11
|
+
|
|
12
|
+
const dependencies = {
|
|
13
|
+
'graphql-type-json': '^0.3.2',
|
|
14
|
+
'@nestjs/graphql': '^12.0.0',
|
|
15
|
+
'@nestjs/common': '^10.0.0',
|
|
16
|
+
'@nestjs/passport': '^10.0.0',
|
|
17
|
+
'@nestjs/axios': '^3.0.0',
|
|
18
|
+
'@prisma/client': '^6.9.0',
|
|
19
|
+
'@apollo/server': '^4.9.0',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const devDependencies = {}
|
|
23
|
+
|
|
24
|
+
await installPlugins(tree, dependencies, devDependencies)
|
|
25
|
+
|
|
26
|
+
await apiLibraryGenerator(tree, { name: 'core', overwrite }, templateRootPath, 'data-access')
|
|
27
|
+
await apiLibraryGenerator(tree, { name: 'core', overwrite }, templateRootPath, 'feature', true)
|
|
28
|
+
await apiLibraryGenerator(tree, { name: 'core', overwrite }, templateRootPath, 'models')
|
|
29
|
+
|
|
30
|
+
await formatFiles(tree)
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
installPackagesTask(tree)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "ApiCore",
|
|
4
|
+
"title": "Create the API Core library",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"overwrite": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"description": "Whether to overwrite existing core libraries if they exist.",
|
|
10
|
+
"default": false
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Tree } from '@nx/devkit'
|
|
2
|
+
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
|
|
3
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
|
4
|
+
import { customGeneratorLogic, CustomGeneratorDependencies } from './generator'
|
|
5
|
+
|
|
6
|
+
const dmmf = {
|
|
7
|
+
datamodel: {
|
|
8
|
+
models: [
|
|
9
|
+
{
|
|
10
|
+
name: 'User',
|
|
11
|
+
fields: [
|
|
12
|
+
{ name: 'id', type: 'Int', isId: true },
|
|
13
|
+
{ name: 'name', type: 'String' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('custom-generator', () => {
|
|
21
|
+
let tree: Tree
|
|
22
|
+
let mockDependencies: CustomGeneratorDependencies
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
tree = createTreeWithEmptyWorkspace()
|
|
26
|
+
mockDependencies = {
|
|
27
|
+
formatFiles: vi.fn(),
|
|
28
|
+
installPackagesTask: vi.fn(),
|
|
29
|
+
getDMMF: vi.fn().mockResolvedValue(dmmf),
|
|
30
|
+
addToModules: vi.fn(),
|
|
31
|
+
apiLibraryGenerator: vi.fn(),
|
|
32
|
+
getPrismaSchemaPath: vi.fn(() => 'prisma/schema.prisma'),
|
|
33
|
+
readPrismaSchema: vi.fn(() => `model User {}`),
|
|
34
|
+
execSync: vi.fn(),
|
|
35
|
+
getNpmScope: vi.fn(() => 'test-scope'),
|
|
36
|
+
pluralize: vi.fn((name: string) => (name.endsWith('s') ? name : name + 's')) as any,
|
|
37
|
+
join: vi.fn((...args: string[]) => args.join('/')),
|
|
38
|
+
}
|
|
39
|
+
vi.clearAllMocks()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should generate files correctly', async () => {
|
|
43
|
+
await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
|
|
44
|
+
expect(mockDependencies.apiLibraryGenerator).toHaveBeenCalledWith(
|
|
45
|
+
tree,
|
|
46
|
+
{ name: 'custom' },
|
|
47
|
+
'',
|
|
48
|
+
undefined,
|
|
49
|
+
false,
|
|
50
|
+
)
|
|
51
|
+
// Check if files are generated in the new structure
|
|
52
|
+
expect(tree.exists('libs/api/custom/src/lib/default/user/user.service.ts')).toBe(true)
|
|
53
|
+
expect(tree.exists('libs/api/custom/src/lib/default/user/user.resolver.ts')).toBe(true)
|
|
54
|
+
expect(tree.exists('libs/api/custom/src/lib/default/user/user.module.ts')).toBe(true)
|
|
55
|
+
expect(tree.exists('libs/api/custom/src/index.ts')).toBe(true)
|
|
56
|
+
expect(tree.exists('libs/api/custom/src/lib/plugins')).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should be idempotent and not overwrite existing model folders', async () => {
|
|
60
|
+
// First run
|
|
61
|
+
await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
|
|
62
|
+
// Write a marker file to simulate user customization
|
|
63
|
+
tree.write('libs/api/custom/src/lib/default/user/custom.txt', 'do not overwrite')
|
|
64
|
+
// Second run
|
|
65
|
+
await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
|
|
66
|
+
// The marker file should still exist
|
|
67
|
+
expect(tree.read('libs/api/custom/src/lib/default/user/custom.txt', 'utf-8')).toBe('do not overwrite')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should not generate files if no models are found', async () => {
|
|
71
|
+
mockDependencies.getDMMF = vi.fn().mockResolvedValue({ datamodel: { models: [] } })
|
|
72
|
+
await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
|
|
73
|
+
expect(mockDependencies.execSync).not.toHaveBeenCalled()
|
|
74
|
+
})
|
|
75
|
+
})
|