servcraft 0.3.1 → 0.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "servcraft",
3
- "version": "0.3.1",
3
+ "version": "0.4.3",
4
4
  "description": "A modular, production-ready Node.js backend framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,6 +48,7 @@ import { dynamicPrismaTemplate } from '../templates/dynamic-prisma.js';
48
48
  import { controllerTestTemplate } from '../templates/controller-test.js';
49
49
  import { serviceTestTemplate } from '../templates/service-test.js';
50
50
  import { integrationTestTemplate } from '../templates/integration-test.js';
51
+ import { getTemplate } from '../utils/template-loader.js';
51
52
 
52
53
  export const generateCommand = new Command('generate')
53
54
  .alias('g')
@@ -100,41 +101,50 @@ generateCommand
100
101
  // Use dynamic templates if fields are provided
101
102
  const hasFields = fields.length > 0;
102
103
 
104
+ // Load templates (custom or built-in)
105
+ const controllerTpl = await getTemplate('controller', controllerTemplate);
106
+ const serviceTpl = await getTemplate('service', serviceTemplate);
107
+ const repositoryTpl = await getTemplate('repository', repositoryTemplate);
108
+ const typesTpl = await getTemplate('types', typesTemplate);
109
+ const schemasTpl = await getTemplate('schemas', schemasTemplate);
110
+ const routesTpl = await getTemplate('routes', routesTemplate);
111
+ const moduleIndexTpl = await getTemplate('module-index', moduleIndexTemplate);
112
+
103
113
  const files = [
104
114
  {
105
115
  name: `${kebabName}.types.ts`,
106
116
  content: hasFields
107
117
  ? dynamicTypesTemplate(kebabName, pascalName, fields)
108
- : typesTemplate(kebabName, pascalName),
118
+ : typesTpl(kebabName, pascalName),
109
119
  },
110
120
  {
111
121
  name: `${kebabName}.schemas.ts`,
112
122
  content: hasFields
113
123
  ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType)
114
- : schemasTemplate(kebabName, pascalName, camelName),
124
+ : schemasTpl(kebabName, pascalName, camelName),
115
125
  },
116
126
  {
117
127
  name: `${kebabName}.service.ts`,
118
- content: serviceTemplate(kebabName, pascalName, camelName),
128
+ content: serviceTpl(kebabName, pascalName, camelName),
119
129
  },
120
130
  {
121
131
  name: `${kebabName}.controller.ts`,
122
- content: controllerTemplate(kebabName, pascalName, camelName),
132
+ content: controllerTpl(kebabName, pascalName, camelName),
123
133
  },
124
- { name: 'index.ts', content: moduleIndexTemplate(kebabName, pascalName, camelName) },
134
+ { name: 'index.ts', content: moduleIndexTpl(kebabName, pascalName, camelName) },
125
135
  ];
126
136
 
127
137
  if (options.repository !== false) {
128
138
  files.push({
129
139
  name: `${kebabName}.repository.ts`,
130
- content: repositoryTemplate(kebabName, pascalName, camelName, pluralName),
140
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName),
131
141
  });
132
142
  }
133
143
 
134
144
  if (options.routes !== false) {
135
145
  files.push({
136
146
  name: `${kebabName}.routes.ts`,
137
- content: routesTemplate(kebabName, pascalName, camelName, pluralName),
147
+ content: routesTpl(kebabName, pascalName, camelName, pluralName),
138
148
  });
139
149
  }
140
150
 
@@ -147,19 +157,24 @@ generateCommand
147
157
  if (options.withTests) {
148
158
  const testDir = path.join(moduleDir, '__tests__');
149
159
 
160
+ // Load test templates (custom or built-in)
161
+ const controllerTestTpl = await getTemplate('controller-test', controllerTestTemplate);
162
+ const serviceTestTpl = await getTemplate('service-test', serviceTestTemplate);
163
+ const integrationTestTpl = await getTemplate('integration-test', integrationTestTemplate);
164
+
150
165
  await writeFile(
151
166
  path.join(testDir, `${kebabName}.controller.test.ts`),
152
- controllerTestTemplate(kebabName, pascalName, camelName)
167
+ controllerTestTpl(kebabName, pascalName, camelName)
153
168
  );
154
169
 
155
170
  await writeFile(
156
171
  path.join(testDir, `${kebabName}.service.test.ts`),
157
- serviceTestTemplate(kebabName, pascalName, camelName)
172
+ serviceTestTpl(kebabName, pascalName, camelName)
158
173
  );
159
174
 
160
175
  await writeFile(
161
176
  path.join(testDir, `${kebabName}.integration.test.ts`),
162
- integrationTestTemplate(kebabName, pascalName, camelName)
177
+ integrationTestTpl(kebabName, pascalName, camelName)
163
178
  );
164
179
  }
165
180
 
@@ -0,0 +1,211 @@
1
+ /* eslint-disable no-console */
2
+ import { Command } from 'commander';
3
+ import path from 'path';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import {
7
+ toPascalCase,
8
+ toCamelCase,
9
+ toKebabCase,
10
+ pluralize,
11
+ writeFile,
12
+ success,
13
+ info,
14
+ getModulesDir,
15
+ } from '../utils/helpers.js';
16
+ import { DryRunManager } from '../utils/dry-run.js';
17
+ import { parseFields } from '../utils/field-parser.js';
18
+ import { dynamicTypesTemplate } from '../templates/dynamic-types.js';
19
+ import { dynamicSchemasTemplate, type ValidatorType } from '../templates/dynamic-schemas.js';
20
+ import { dynamicPrismaTemplate } from '../templates/dynamic-prisma.js';
21
+ import { controllerTemplate } from '../templates/controller.js';
22
+ import { serviceTemplate } from '../templates/service.js';
23
+ import { repositoryTemplate } from '../templates/repository.js';
24
+ import { routesTemplate } from '../templates/routes.js';
25
+ import { moduleIndexTemplate } from '../templates/module-index.js';
26
+ import { controllerTestTemplate } from '../templates/controller-test.js';
27
+ import { serviceTestTemplate } from '../templates/service-test.js';
28
+ import { integrationTestTemplate } from '../templates/integration-test.js';
29
+ import { getTemplate } from '../utils/template-loader.js';
30
+
31
+ export const scaffoldCommand = new Command('scaffold')
32
+ .description('Generate complete CRUD with Prisma model')
33
+ .argument('<name>', 'Resource name (e.g., product, user)')
34
+ .option(
35
+ '--fields <fields>',
36
+ 'Field definitions: "name:string email:string? age:number category:relation"'
37
+ )
38
+ .option('--validator <type>', 'Validator type: zod, joi, yup', 'zod')
39
+ .option('--dry-run', 'Preview changes without writing files')
40
+ .action(
41
+ async (
42
+ name: string,
43
+ options: { fields?: string; validator?: ValidatorType; dryRun?: boolean }
44
+ ) => {
45
+ const dryRun = DryRunManager.getInstance();
46
+ if (options.dryRun) {
47
+ dryRun.enable();
48
+ console.log(chalk.yellow('\n⚠ DRY RUN MODE - No files will be written\n'));
49
+ }
50
+
51
+ if (!options.fields) {
52
+ console.log(chalk.red('\nāœ— Error: --fields option is required\n'));
53
+ console.log(chalk.gray('Example:'));
54
+ console.log(
55
+ chalk.cyan(
56
+ ' servcraft scaffold product --fields "name:string price:number category:relation"'
57
+ )
58
+ );
59
+ process.exit(1);
60
+ }
61
+
62
+ const spinner = ora('Scaffolding resource...').start();
63
+
64
+ try {
65
+ // Parse fields
66
+ const fields = parseFields(options.fields || '');
67
+
68
+ if (!fields || fields.length === 0) {
69
+ spinner.fail('No valid fields provided');
70
+ console.log(chalk.gray(`\nReceived: ${options.fields}`));
71
+ console.log(chalk.gray(`Parsed: ${JSON.stringify(fields)}`));
72
+ process.exit(1);
73
+ }
74
+
75
+ // Generate names
76
+ const kebabName = toKebabCase(name);
77
+ const pascalName = toPascalCase(name);
78
+ const camelName = toCamelCase(name);
79
+ const pluralName = pluralize(camelName);
80
+ const tableName = pluralize(kebabName);
81
+
82
+ // Create module directory
83
+ const modulesDir = getModulesDir();
84
+ const moduleDir = path.join(modulesDir, kebabName);
85
+
86
+ // Load templates (custom or built-in)
87
+ const controllerTpl = await getTemplate('controller', controllerTemplate);
88
+ const serviceTpl = await getTemplate('service', serviceTemplate);
89
+ const repositoryTpl = await getTemplate('repository', repositoryTemplate);
90
+ const routesTpl = await getTemplate('routes', routesTemplate);
91
+ const moduleIndexTpl = await getTemplate('module-index', moduleIndexTemplate);
92
+
93
+ // Generate all files
94
+ const files = [
95
+ {
96
+ name: `${kebabName}.types.ts`,
97
+ content: dynamicTypesTemplate(kebabName, pascalName, fields),
98
+ },
99
+ {
100
+ name: `${kebabName}.schemas.ts`,
101
+ content: dynamicSchemasTemplate(
102
+ kebabName,
103
+ pascalName,
104
+ camelName,
105
+ fields,
106
+ options.validator || 'zod'
107
+ ),
108
+ },
109
+ {
110
+ name: `${kebabName}.service.ts`,
111
+ content: serviceTpl(kebabName, pascalName, camelName),
112
+ },
113
+ {
114
+ name: `${kebabName}.controller.ts`,
115
+ content: controllerTpl(kebabName, pascalName, camelName),
116
+ },
117
+ {
118
+ name: 'index.ts',
119
+ content: moduleIndexTpl(kebabName, pascalName, camelName),
120
+ },
121
+ {
122
+ name: `${kebabName}.repository.ts`,
123
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName),
124
+ },
125
+ {
126
+ name: `${kebabName}.routes.ts`,
127
+ content: routesTpl(kebabName, pascalName, camelName, pluralName),
128
+ },
129
+ ];
130
+
131
+ // Write all files
132
+ for (const file of files) {
133
+ await writeFile(path.join(moduleDir, file.name), file.content);
134
+ }
135
+
136
+ // Generate test files
137
+ const testDir = path.join(moduleDir, '__tests__');
138
+
139
+ // Load test templates (custom or built-in)
140
+ const controllerTestTpl = await getTemplate('controller-test', controllerTestTemplate);
141
+ const serviceTestTpl = await getTemplate('service-test', serviceTestTemplate);
142
+ const integrationTestTpl = await getTemplate('integration-test', integrationTestTemplate);
143
+
144
+ await writeFile(
145
+ path.join(testDir, `${kebabName}.controller.test.ts`),
146
+ controllerTestTpl(kebabName, pascalName, camelName)
147
+ );
148
+
149
+ await writeFile(
150
+ path.join(testDir, `${kebabName}.service.test.ts`),
151
+ serviceTestTpl(kebabName, pascalName, camelName)
152
+ );
153
+
154
+ await writeFile(
155
+ path.join(testDir, `${kebabName}.integration.test.ts`),
156
+ integrationTestTpl(kebabName, pascalName, camelName)
157
+ );
158
+
159
+ spinner.succeed(`Resource "${pascalName}" scaffolded successfully!`);
160
+
161
+ // Show Prisma model
162
+ console.log('\n' + '─'.repeat(70));
163
+ info('šŸ“‹ Prisma model to add to schema.prisma:');
164
+ console.log(chalk.gray('\n// Copy this to your schema.prisma file:\n'));
165
+ console.log(dynamicPrismaTemplate(pascalName, tableName, fields));
166
+ console.log('─'.repeat(70));
167
+
168
+ // Show fields summary
169
+ console.log('\nšŸ“‹ Fields scaffolded:');
170
+ fields.forEach((f) => {
171
+ const opts = [];
172
+ if (f.isOptional) opts.push('optional');
173
+ if (f.isArray) opts.push('array');
174
+ if (f.isUnique) opts.push('unique');
175
+ if (f.relation) opts.push(`relation: ${f.relation.model}`);
176
+ const optsStr = opts.length > 0 ? ` (${opts.join(', ')})` : '';
177
+ success(` ${f.name}: ${f.type}${optsStr}`);
178
+ });
179
+
180
+ // Show files created
181
+ console.log('\nšŸ“ Files created:');
182
+ files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
183
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
184
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
185
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
186
+
187
+ // Show next steps
188
+ console.log('\nšŸ“Œ Next steps:');
189
+ info(' 1. Add the Prisma model to your schema.prisma file');
190
+ info(' 2. Run: npx prisma db push (or prisma migrate dev)');
191
+ info(' 3. Run: npx prisma generate');
192
+ info(' 4. Register the module routes in your app');
193
+ info(' 5. Update the test files with actual test data');
194
+
195
+ console.log(
196
+ chalk.gray('\nšŸ’” Tip: Use --dry-run to preview changes before applying them\n')
197
+ );
198
+
199
+ if (options.dryRun) {
200
+ dryRun.printSummary();
201
+ }
202
+ } catch (error) {
203
+ spinner.fail('Failed to scaffold resource');
204
+ if (error instanceof Error) {
205
+ console.error(chalk.red(`\nāœ— ${error.message}\n`));
206
+ console.error(chalk.gray(error.stack));
207
+ }
208
+ process.exit(1);
209
+ }
210
+ }
211
+ );
@@ -0,0 +1,147 @@
1
+ /* eslint-disable no-console */
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { getProjectRoot } from '../utils/helpers.js';
7
+ import { validateProject, displayError } from '../utils/error-handler.js';
8
+
9
+ // Template types available for customization
10
+ const TEMPLATE_TYPES = [
11
+ 'controller',
12
+ 'service',
13
+ 'repository',
14
+ 'types',
15
+ 'schemas',
16
+ 'routes',
17
+ 'module-index',
18
+ 'controller-test',
19
+ 'service-test',
20
+ 'integration-test',
21
+ ];
22
+
23
+ async function initTemplates(): Promise<void> {
24
+ const projectError = validateProject();
25
+ if (projectError) {
26
+ displayError(projectError);
27
+ return;
28
+ }
29
+
30
+ const projectRoot = getProjectRoot();
31
+ const templatesDir = path.join(projectRoot, '.servcraft', 'templates');
32
+
33
+ try {
34
+ // Create .servcraft/templates directory
35
+ await fs.mkdir(templatesDir, { recursive: true });
36
+
37
+ console.log(chalk.cyan('\nšŸ“ Creating custom template directory...\n'));
38
+
39
+ // Create example template files
40
+ const exampleController = `// Custom controller template
41
+ // Available variables: name, pascalName, camelName, pluralName
42
+ export function controllerTemplate(name: string, pascalName: string, camelName: string): string {
43
+ return \`import type { FastifyRequest, FastifyReply } from 'fastify';
44
+ import type { \${pascalName}Service } from './\${name}.service.js';
45
+
46
+ export class \${pascalName}Controller {
47
+ constructor(private \${camelName}Service: \${pascalName}Service) {}
48
+
49
+ // Add your custom controller methods here
50
+ async getAll(request: FastifyRequest, reply: FastifyReply) {
51
+ const data = await this.\${camelName}Service.getAll();
52
+ return reply.send({ data });
53
+ }
54
+ }
55
+ \`;
56
+ }
57
+ `;
58
+
59
+ await fs.writeFile(
60
+ path.join(templatesDir, 'controller.example.ts'),
61
+ exampleController,
62
+ 'utf-8'
63
+ );
64
+
65
+ console.log(chalk.green('āœ” Created template directory: .servcraft/templates/'));
66
+ console.log(chalk.green('āœ” Created example template: controller.example.ts\n'));
67
+
68
+ console.log(chalk.bold('šŸ“‹ Available template types:\n'));
69
+ TEMPLATE_TYPES.forEach((type) => {
70
+ console.log(chalk.gray(` • ${type}.ts`));
71
+ });
72
+
73
+ console.log(chalk.yellow('\nšŸ’” To customize a template:'));
74
+ console.log(chalk.gray(' 1. Copy the example template'));
75
+ console.log(chalk.gray(' 2. Rename it (remove .example)'));
76
+ console.log(chalk.gray(' 3. Modify the template code'));
77
+ console.log(chalk.gray(' 4. Use --template flag when generating\n'));
78
+ } catch (error) {
79
+ if (error instanceof Error) {
80
+ console.error(chalk.red(`\nāœ— Failed to initialize templates: ${error.message}\n`));
81
+ }
82
+ }
83
+ }
84
+
85
+ async function listTemplates(): Promise<void> {
86
+ const projectError = validateProject();
87
+ if (projectError) {
88
+ displayError(projectError);
89
+ return;
90
+ }
91
+
92
+ const projectRoot = getProjectRoot();
93
+ const projectTemplatesDir = path.join(projectRoot, '.servcraft', 'templates');
94
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
95
+ const userTemplatesDir = path.join(homeDir, '.servcraft', 'templates');
96
+
97
+ console.log(chalk.bold.cyan('\nšŸ“‹ Available Templates\n'));
98
+
99
+ // Check project templates
100
+ console.log(chalk.bold('Project templates (.servcraft/templates/):'));
101
+ try {
102
+ const files = await fs.readdir(projectTemplatesDir);
103
+ const templates = files.filter((f) => f.endsWith('.ts') && !f.endsWith('.example.ts'));
104
+
105
+ if (templates.length > 0) {
106
+ templates.forEach((t) => {
107
+ console.log(chalk.green(` āœ“ ${t}`));
108
+ });
109
+ } else {
110
+ console.log(chalk.gray(' (none)'));
111
+ }
112
+ } catch {
113
+ console.log(chalk.gray(' (directory not found)'));
114
+ }
115
+
116
+ // Check user templates
117
+ console.log(chalk.bold('\nUser templates (~/.servcraft/templates/):'));
118
+ try {
119
+ const files = await fs.readdir(userTemplatesDir);
120
+ const templates = files.filter((f) => f.endsWith('.ts') && !f.endsWith('.example.ts'));
121
+
122
+ if (templates.length > 0) {
123
+ templates.forEach((t) => {
124
+ console.log(chalk.green(` āœ“ ${t}`));
125
+ });
126
+ } else {
127
+ console.log(chalk.gray(' (none)'));
128
+ }
129
+ } catch {
130
+ console.log(chalk.gray(' (directory not found)'));
131
+ }
132
+
133
+ // Show built-in templates
134
+ console.log(chalk.bold('\nBuilt-in templates:'));
135
+ TEMPLATE_TYPES.forEach((t) => {
136
+ console.log(chalk.cyan(` • ${t}.ts`));
137
+ });
138
+
139
+ console.log(chalk.gray('\nšŸ’” Run "servcraft templates init" to create custom templates\n'));
140
+ }
141
+
142
+ export const templatesCommand = new Command('templates')
143
+ .description('Manage code generation templates')
144
+ .addCommand(
145
+ new Command('init').description('Initialize custom templates directory').action(initTemplates)
146
+ )
147
+ .addCommand(new Command('list').description('List available templates').action(listTemplates));
package/src/cli/index.ts CHANGED
@@ -11,6 +11,8 @@ import { removeCommand } from './commands/remove.js';
11
11
  import { doctorCommand } from './commands/doctor.js';
12
12
  import { updateCommand } from './commands/update.js';
13
13
  import { completionCommand } from './commands/completion.js';
14
+ import { scaffoldCommand } from './commands/scaffold.js';
15
+ import { templatesCommand } from './commands/templates.js';
14
16
 
15
17
  const program = new Command();
16
18
 
@@ -49,4 +51,10 @@ program.addCommand(updateCommand);
49
51
  // Shell completion
50
52
  program.addCommand(completionCommand);
51
53
 
54
+ // Scaffold resource
55
+ program.addCommand(scaffoldCommand);
56
+
57
+ // Template management
58
+ program.addCommand(templatesCommand);
59
+
52
60
  program.parse();
@@ -0,0 +1,80 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { getProjectRoot } from './helpers.js';
4
+
5
+ /**
6
+ * Template loader that checks for custom templates in priority order:
7
+ * 1. Project templates (.servcraft/templates/)
8
+ * 2. User templates (~/.servcraft/templates/)
9
+ * 3. Built-in templates (fallback)
10
+ */
11
+
12
+ export type TemplateType =
13
+ | 'controller'
14
+ | 'service'
15
+ | 'repository'
16
+ | 'types'
17
+ | 'schemas'
18
+ | 'routes'
19
+ | 'module-index'
20
+ | 'controller-test'
21
+ | 'service-test'
22
+ | 'integration-test';
23
+
24
+ interface TemplateFunction {
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ [key: string]: (...args: any[]) => string;
27
+ }
28
+
29
+ /**
30
+ * Load a custom template if available, otherwise return null
31
+ */
32
+ export async function loadCustomTemplate(
33
+ templateType: TemplateType
34
+ ): Promise<TemplateFunction | null> {
35
+ const projectRoot = getProjectRoot();
36
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
37
+
38
+ const locations = [
39
+ path.join(projectRoot, '.servcraft', 'templates', `${templateType}.ts`),
40
+ path.join(homeDir, '.servcraft', 'templates', `${templateType}.ts`),
41
+ ];
42
+
43
+ for (const location of locations) {
44
+ try {
45
+ await fs.access(location);
46
+ // Template file exists, dynamically import it
47
+ const templateModule = (await import(`file://${location}`)) as TemplateFunction;
48
+
49
+ // Look for the template function (e.g., controllerTemplate, serviceTemplate)
50
+ const functionName = `${templateType.replace(/-/g, '')}Template`;
51
+
52
+ if (templateModule[functionName]) {
53
+ return templateModule;
54
+ }
55
+ } catch {
56
+ // File doesn't exist or import failed, try next location
57
+ continue;
58
+ }
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Get template function with fallback to built-in
66
+ */
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ export async function getTemplate<T extends (...args: any[]) => string>(
69
+ templateType: TemplateType,
70
+ builtInTemplate: T
71
+ ): Promise<T> {
72
+ const customTemplate = await loadCustomTemplate(templateType);
73
+
74
+ if (customTemplate) {
75
+ const functionName = `${templateType.replace(/-/g, '')}Template`;
76
+ return customTemplate[functionName] as T;
77
+ }
78
+
79
+ return builtInTemplate;
80
+ }
@@ -2,7 +2,24 @@ import { prisma } from '../../database/prisma.js';
2
2
  import type { PaginatedResult, PaginationParams } from '../../types/index.js';
3
3
  import { createPaginatedResult, getSkip } from '../../utils/pagination.js';
4
4
  import type { User, CreateUserData, UpdateUserData, UserFilters } from './types.js';
5
- import { UserRole, UserStatus } from '@prisma/client';
5
+
6
+ // Use string literal enums for ESM/CommonJS compatibility
7
+ const UserRole = {
8
+ USER: 'USER',
9
+ MODERATOR: 'MODERATOR',
10
+ ADMIN: 'ADMIN',
11
+ SUPER_ADMIN: 'SUPER_ADMIN',
12
+ } as const;
13
+
14
+ const UserStatus = {
15
+ ACTIVE: 'ACTIVE',
16
+ INACTIVE: 'INACTIVE',
17
+ SUSPENDED: 'SUSPENDED',
18
+ BANNED: 'BANNED',
19
+ } as const;
20
+
21
+ type UserRole = (typeof UserRole)[keyof typeof UserRole];
22
+ type UserStatus = (typeof UserStatus)[keyof typeof UserStatus];
6
23
 
7
24
  /**
8
25
  * User Repository - Prisma Implementation