@currentjs/gen 0.1.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 +7 -0
- package/LICENSE +56 -0
- package/README.md +686 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +143 -0
- package/dist/commands/commit.d.ts +1 -0
- package/dist/commands/commit.js +153 -0
- package/dist/commands/createApp.d.ts +1 -0
- package/dist/commands/createApp.js +64 -0
- package/dist/commands/createModule.d.ts +1 -0
- package/dist/commands/createModule.js +121 -0
- package/dist/commands/diff.d.ts +1 -0
- package/dist/commands/diff.js +164 -0
- package/dist/commands/generateAll.d.ts +4 -0
- package/dist/commands/generateAll.js +305 -0
- package/dist/commands/infer.d.ts +1 -0
- package/dist/commands/infer.js +179 -0
- package/dist/generators/controllerGenerator.d.ts +20 -0
- package/dist/generators/controllerGenerator.js +280 -0
- package/dist/generators/domainModelGenerator.d.ts +33 -0
- package/dist/generators/domainModelGenerator.js +175 -0
- package/dist/generators/serviceGenerator.d.ts +39 -0
- package/dist/generators/serviceGenerator.js +379 -0
- package/dist/generators/storeGenerator.d.ts +31 -0
- package/dist/generators/storeGenerator.js +191 -0
- package/dist/generators/templateGenerator.d.ts +11 -0
- package/dist/generators/templateGenerator.js +143 -0
- package/dist/generators/templates/appTemplates.d.ts +27 -0
- package/dist/generators/templates/appTemplates.js +1621 -0
- package/dist/generators/templates/controllerTemplates.d.ts +43 -0
- package/dist/generators/templates/controllerTemplates.js +82 -0
- package/dist/generators/templates/index.d.ts +5 -0
- package/dist/generators/templates/index.js +21 -0
- package/dist/generators/templates/serviceTemplates.d.ts +15 -0
- package/dist/generators/templates/serviceTemplates.js +54 -0
- package/dist/generators/templates/storeTemplates.d.ts +9 -0
- package/dist/generators/templates/storeTemplates.js +260 -0
- package/dist/generators/templates/validationTemplates.d.ts +25 -0
- package/dist/generators/templates/validationTemplates.js +66 -0
- package/dist/generators/templates/viewTemplates.d.ts +16 -0
- package/dist/generators/templates/viewTemplates.js +359 -0
- package/dist/generators/validationGenerator.d.ts +24 -0
- package/dist/generators/validationGenerator.js +199 -0
- package/dist/utils/cliUtils.d.ts +6 -0
- package/dist/utils/cliUtils.js +71 -0
- package/dist/utils/colors.d.ts +26 -0
- package/dist/utils/colors.js +80 -0
- package/dist/utils/commitUtils.d.ts +46 -0
- package/dist/utils/commitUtils.js +377 -0
- package/dist/utils/constants.d.ts +52 -0
- package/dist/utils/constants.js +64 -0
- package/dist/utils/generationRegistry.d.ts +25 -0
- package/dist/utils/generationRegistry.js +192 -0
- package/howto.md +556 -0
- package/package.json +44 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const controllerTemplates: {
|
|
2
|
+
controllerClass: string;
|
|
3
|
+
controllerMethod: string;
|
|
4
|
+
userExtraction: string;
|
|
5
|
+
methodImplementations: {
|
|
6
|
+
list: string;
|
|
7
|
+
get: string;
|
|
8
|
+
create: string;
|
|
9
|
+
update: string;
|
|
10
|
+
delete: string;
|
|
11
|
+
empty: string;
|
|
12
|
+
};
|
|
13
|
+
responseFormats: {
|
|
14
|
+
list: string;
|
|
15
|
+
get: string;
|
|
16
|
+
create: string;
|
|
17
|
+
update: string;
|
|
18
|
+
delete: string;
|
|
19
|
+
};
|
|
20
|
+
statusCodes: {
|
|
21
|
+
list: {
|
|
22
|
+
success: number;
|
|
23
|
+
error: number;
|
|
24
|
+
};
|
|
25
|
+
get: {
|
|
26
|
+
success: number;
|
|
27
|
+
error: number;
|
|
28
|
+
};
|
|
29
|
+
create: {
|
|
30
|
+
success: number;
|
|
31
|
+
error: number;
|
|
32
|
+
};
|
|
33
|
+
update: {
|
|
34
|
+
success: number;
|
|
35
|
+
error: number;
|
|
36
|
+
};
|
|
37
|
+
delete: {
|
|
38
|
+
success: number;
|
|
39
|
+
error: number;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export declare const controllerFileTemplate = "import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';\nimport { {{ENTITY_NAME}}Service } from '../../application/services/{{ENTITY_NAME}}Service';\nimport { {{ENTITY_NAME}}DTO } from '../../application/validation/{{ENTITY_NAME}}Validation';{{JWT_IMPORT}}\nimport { Get, Post, Put, Patch, Delete, Controller, Render } from '@currentjs/router';\nimport type { IContext } from '@currentjs/router';\n\n{{CONTROLLER_CLASS}}";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.controllerFileTemplate = exports.controllerTemplates = void 0;
|
|
4
|
+
exports.controllerTemplates = {
|
|
5
|
+
controllerClass: `@Controller('{{CONTROLLER_BASE}}', {})
|
|
6
|
+
export class {{CONTROLLER_NAME}} {
|
|
7
|
+
constructor(
|
|
8
|
+
private {{ENTITY_LOWER}}Service: {{ENTITY_NAME}}Service
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
{{CONTROLLER_METHODS}}
|
|
12
|
+
}`,
|
|
13
|
+
controllerMethod: ` @{{HTTP_DECORATOR}}("{{ENDPOINT_PATH}}"){{RENDER_DECORATOR}}
|
|
14
|
+
async {{METHOD_NAME}}(context: IContext): Promise<{{RETURN_TYPE}}> {
|
|
15
|
+
{{METHOD_IMPLEMENTATION}}
|
|
16
|
+
}`,
|
|
17
|
+
userExtraction: ` const user = context.request.user;
|
|
18
|
+
if (!user) {
|
|
19
|
+
throw new Error('User authentication required');
|
|
20
|
+
}`,
|
|
21
|
+
methodImplementations: {
|
|
22
|
+
list: `{{USER_EXTRACTION}}
|
|
23
|
+
// Extract pagination from URL parameters
|
|
24
|
+
const page = parseInt(context.request.parameters.page as string) || 1;
|
|
25
|
+
const limit = parseInt(context.request.parameters.limit as string) || 10;
|
|
26
|
+
|
|
27
|
+
const {{ENTITY_LOWER}}s = await this.{{ENTITY_LOWER}}Service.list(page, limit{{USER_PARAM}});
|
|
28
|
+
return {{ENTITY_LOWER}}s;`,
|
|
29
|
+
get: `{{USER_EXTRACTION}}
|
|
30
|
+
const id = parseInt(context.request.parameters.id as string);
|
|
31
|
+
if (isNaN(id)) {
|
|
32
|
+
throw new Error('Invalid ID parameter');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {{ENTITY_LOWER}} = await this.{{ENTITY_LOWER}}Service.get(id{{USER_PARAM}});
|
|
36
|
+
return {{ENTITY_LOWER}};`,
|
|
37
|
+
create: `{{USER_EXTRACTION}}
|
|
38
|
+
const new{{ENTITY_NAME}} = await this.{{ENTITY_LOWER}}Service.create(context.request.body as {{ENTITY_NAME}}DTO{{USER_PARAM}});
|
|
39
|
+
return new{{ENTITY_NAME}};`,
|
|
40
|
+
update: `{{USER_EXTRACTION}}
|
|
41
|
+
const id = parseInt(context.request.parameters.id as string);
|
|
42
|
+
if (isNaN(id)) {
|
|
43
|
+
throw new Error('Invalid ID parameter');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const updated{{ENTITY_NAME}} = await this.{{ENTITY_LOWER}}Service.update(id, context.request.body as {{ENTITY_NAME}}DTO{{USER_PARAM}});
|
|
47
|
+
return updated{{ENTITY_NAME}};`,
|
|
48
|
+
delete: `{{USER_EXTRACTION}}
|
|
49
|
+
const id = parseInt(context.request.parameters.id as string);
|
|
50
|
+
if (isNaN(id)) {
|
|
51
|
+
throw new Error('Invalid ID parameter');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = await this.{{ENTITY_LOWER}}Service.delete(id{{USER_PARAM}});
|
|
55
|
+
return result;`,
|
|
56
|
+
empty: `{{USER_EXTRACTION}}
|
|
57
|
+
// Provide an empty/default {{ENTITY_NAME}} for create form rendering
|
|
58
|
+
// Note: actual create happens via API endpoint using custom form handling
|
|
59
|
+
return {} as any;`
|
|
60
|
+
},
|
|
61
|
+
responseFormats: {
|
|
62
|
+
list: `{ data: {{ENTITY_LOWER}}s, page, limit }`,
|
|
63
|
+
get: `{ data: {{ENTITY_LOWER}} }`,
|
|
64
|
+
create: `{ data: new{{ENTITY_NAME}}, message: '{{ENTITY_NAME}} created successfully' }`,
|
|
65
|
+
update: `{ data: updated{{ENTITY_NAME}}, message: '{{ENTITY_NAME}} updated successfully' }`,
|
|
66
|
+
delete: `result`
|
|
67
|
+
},
|
|
68
|
+
statusCodes: {
|
|
69
|
+
list: { success: 200, error: 500 },
|
|
70
|
+
get: { success: 200, error: 404 },
|
|
71
|
+
create: { success: 201, error: 400 },
|
|
72
|
+
update: { success: 200, error: 400 },
|
|
73
|
+
delete: { success: 200, error: 400 }
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
exports.controllerFileTemplate = `import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
|
|
77
|
+
import { {{ENTITY_NAME}}Service } from '../../application/services/{{ENTITY_NAME}}Service';
|
|
78
|
+
import { {{ENTITY_NAME}}DTO } from '../../application/validation/{{ENTITY_NAME}}Validation';{{JWT_IMPORT}}
|
|
79
|
+
import { Get, Post, Put, Patch, Delete, Controller, Render } from '@currentjs/router';
|
|
80
|
+
import type { IContext } from '@currentjs/router';
|
|
81
|
+
|
|
82
|
+
{{CONTROLLER_CLASS}}`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./serviceTemplates"), exports);
|
|
18
|
+
__exportStar(require("./controllerTemplates"), exports);
|
|
19
|
+
__exportStar(require("./storeTemplates"), exports);
|
|
20
|
+
__exportStar(require("./validationTemplates"), exports);
|
|
21
|
+
__exportStar(require("./appTemplates"), exports);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const serviceTemplates: {
|
|
2
|
+
serviceClass: string;
|
|
3
|
+
serviceMethod: string;
|
|
4
|
+
permissionCheck: string;
|
|
5
|
+
ownerPermissionCheck: string;
|
|
6
|
+
defaultImplementations: {
|
|
7
|
+
list: string;
|
|
8
|
+
getById: string;
|
|
9
|
+
create: string;
|
|
10
|
+
update: string;
|
|
11
|
+
delete: string;
|
|
12
|
+
};
|
|
13
|
+
customActionImplementation: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const serviceFileTemplate = "import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';\nimport { {{ENTITY_NAME}}Store } from '../../infrastructure/stores/{{ENTITY_NAME}}Store';\nimport { {{ENTITY_NAME}}DTO, validateCreate{{ENTITY_NAME}}, validateUpdate{{ENTITY_NAME}} } from '../validation/{{ENTITY_NAME}}Validation';{{PERMISSIONS_IMPORT}}{{CUSTOM_IMPORTS}}\n\n{{SERVICE_CLASS}}";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serviceFileTemplate = exports.serviceTemplates = void 0;
|
|
4
|
+
exports.serviceTemplates = {
|
|
5
|
+
serviceClass: `export class {{ENTITY_NAME}}Service {
|
|
6
|
+
constructor(
|
|
7
|
+
private {{ENTITY_LOWER}}Store: {{ENTITY_NAME}}Store{{AUTH_SERVICE_PARAM}}
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
{{SERVICE_METHODS}}
|
|
11
|
+
}`,
|
|
12
|
+
serviceMethod: ` async {{METHOD_NAME}}({{METHOD_PARAMS}}{{USER_PARAM}}): Promise<{{RETURN_TYPE}}> {
|
|
13
|
+
{{PERMISSION_CHECK}}
|
|
14
|
+
{{METHOD_IMPLEMENTATION}}
|
|
15
|
+
}`,
|
|
16
|
+
permissionCheck: ` // Role check: {{REQUIRED_ROLES}}
|
|
17
|
+
const allowedRoles = [{{ROLES_ARRAY}}];
|
|
18
|
+
if (allowedRoles.length > 0 && !allowedRoles.includes(user.role)) {
|
|
19
|
+
throw new Error('Insufficient permissions to perform this action');
|
|
20
|
+
}`,
|
|
21
|
+
ownerPermissionCheck: ``,
|
|
22
|
+
defaultImplementations: {
|
|
23
|
+
list: `const {{ENTITY_LOWER}}s = await this.{{ENTITY_LOWER}}Store.getAll(page, limit);
|
|
24
|
+
return {{ENTITY_LOWER}}s;`,
|
|
25
|
+
getById: `const {{ENTITY_LOWER}} = await this.{{ENTITY_LOWER}}Store.getById(id);
|
|
26
|
+
if (!{{ENTITY_LOWER}}) {
|
|
27
|
+
throw new Error('{{ENTITY_NAME}} not found');
|
|
28
|
+
}
|
|
29
|
+
return {{ENTITY_LOWER}};`,
|
|
30
|
+
create: `validateCreate{{ENTITY_NAME}}({{ENTITY_LOWER}}Data);
|
|
31
|
+
const {{ENTITY_LOWER}} = new {{ENTITY_NAME}}(0, {{CONSTRUCTOR_ARGS}});
|
|
32
|
+
return await this.{{ENTITY_LOWER}}Store.insert({{ENTITY_LOWER}});`,
|
|
33
|
+
update: `validateUpdate{{ENTITY_NAME}}({{ENTITY_LOWER}}Data);
|
|
34
|
+
const existing{{ENTITY_NAME}} = await this.{{ENTITY_LOWER}}Store.getById(id);
|
|
35
|
+
if (!existing{{ENTITY_NAME}}) {
|
|
36
|
+
throw new Error('{{ENTITY_NAME}} not found');
|
|
37
|
+
}
|
|
38
|
+
{{UPDATE_SETTER_CALLS}}
|
|
39
|
+
return await this.{{ENTITY_LOWER}}Store.update(id, existing{{ENTITY_NAME}});`,
|
|
40
|
+
delete: `const success = await this.{{ENTITY_LOWER}}Store.softDelete(id);
|
|
41
|
+
if (!success) {
|
|
42
|
+
throw new Error('{{ENTITY_NAME}} not found or could not be deleted');
|
|
43
|
+
}
|
|
44
|
+
return { success: true, message: '{{ENTITY_NAME}} deleted successfully' };`
|
|
45
|
+
},
|
|
46
|
+
customActionImplementation: `// Custom action implementation
|
|
47
|
+
const result = await {{CUSTOM_FUNCTION_CALL}};
|
|
48
|
+
return result;`
|
|
49
|
+
};
|
|
50
|
+
exports.serviceFileTemplate = `import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
|
|
51
|
+
import { {{ENTITY_NAME}}Store } from '../../infrastructure/stores/{{ENTITY_NAME}}Store';
|
|
52
|
+
import { {{ENTITY_NAME}}DTO, validateCreate{{ENTITY_NAME}}, validateUpdate{{ENTITY_NAME}} } from '../validation/{{ENTITY_NAME}}Validation';{{PERMISSIONS_IMPORT}}{{CUSTOM_IMPORTS}}
|
|
53
|
+
|
|
54
|
+
{{SERVICE_CLASS}}`;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileTemplates = exports.storeTemplates = void 0;
|
|
4
|
+
exports.storeTemplates = {
|
|
5
|
+
rowInterface: `export interface {{ENTITY_NAME}}Row {
|
|
6
|
+
id: number;
|
|
7
|
+
{{ROW_FIELDS}}
|
|
8
|
+
created_at: Date;
|
|
9
|
+
updated_at: Date;
|
|
10
|
+
deleted_at?: Date;
|
|
11
|
+
}`,
|
|
12
|
+
storeClass: `export class {{ENTITY_NAME}}Store implements StoreInterface<{{ENTITY_NAME}}, {{ENTITY_NAME}}Row> {
|
|
13
|
+
private static readonly FILTERABLE_FIELDS = [{{FILTERABLE_FIELDS_ARRAY}}];
|
|
14
|
+
private static readonly UPDATABLE_FIELDS = [{{UPDATABLE_FIELDS_ARRAY}}];
|
|
15
|
+
|
|
16
|
+
constructor(private db: ISqlProvider) {}
|
|
17
|
+
|
|
18
|
+
async getById(id: number): Promise<{{ENTITY_NAME}} | null> {
|
|
19
|
+
try {
|
|
20
|
+
const query = 'SELECT * FROM {{TABLE_NAME}} WHERE id = :id AND deleted_at IS NULL';
|
|
21
|
+
const result = await this.db.query(query, { id });
|
|
22
|
+
|
|
23
|
+
if (!result.success || result.data.length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {{ENTITY_NAME}}Store.rowToModel(result.data[0] as {{ENTITY_NAME}}Row);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof MySQLConnectionError) {
|
|
30
|
+
throw new Error(\`Database connection error while fetching {{ENTITY_NAME}} with id \${id}: \${error.message}\`);
|
|
31
|
+
} else if (error instanceof MySQLQueryError) {
|
|
32
|
+
throw new Error(\`Query error while fetching {{ENTITY_NAME}} with id \${id}: \${error.message}\`);
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getAll(page: number = 1, limit: number = 10): Promise<{{ENTITY_NAME}}[]> {
|
|
39
|
+
const offset = (page - 1) * limit;
|
|
40
|
+
const query = \`SELECT * FROM {{TABLE_NAME}} WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT \${limit} OFFSET \${offset}\`;
|
|
41
|
+
const result = await this.db.query(query, {});
|
|
42
|
+
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result.data.map((row: {{ENTITY_NAME}}Row) => {{ENTITY_NAME}}Store.rowToModel(row));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getAllByUserId(userId: number, page: number = 1, limit: number = 10): Promise<{{ENTITY_NAME}}[]> {
|
|
51
|
+
const offset = (page - 1) * limit;
|
|
52
|
+
const query = \`SELECT * FROM {{TABLE_NAME}} WHERE user_id = :userId AND deleted_at IS NULL ORDER BY created_at DESC LIMIT \${limit} OFFSET \${offset}\`;
|
|
53
|
+
const result = await this.db.query(query, { userId });
|
|
54
|
+
|
|
55
|
+
if (!result.success) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result.data.map((row: {{ENTITY_NAME}}Row) => {{ENTITY_NAME}}Store.rowToModel(row));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getBy(filters: Partial<Pick<{{ENTITY_NAME}}Row, {{FILTERABLE_FIELDS}}>>): Promise<{{ENTITY_NAME}} | null> {
|
|
63
|
+
const filterKeys = Object.keys(filters);
|
|
64
|
+
|
|
65
|
+
// Runtime validation against SQL injection
|
|
66
|
+
for (const key of filterKeys) {
|
|
67
|
+
if (!{{ENTITY_NAME}}Store.FILTERABLE_FIELDS.includes(key)) {
|
|
68
|
+
throw new Error(\`Invalid filter field: \${key}\`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const whereConditions = filterKeys.map(key => \`\${key} = :\${key}\`);
|
|
73
|
+
|
|
74
|
+
if (whereConditions.length === 0) {
|
|
75
|
+
throw new Error('At least one filter condition is required');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const query = \`SELECT * FROM {{TABLE_NAME}} WHERE \${whereConditions.join(' AND ')} AND deleted_at IS NULL LIMIT 1\`;
|
|
79
|
+
const result = await this.db.query(query, filters);
|
|
80
|
+
|
|
81
|
+
if (!result.success || result.data.length === 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {{ENTITY_NAME}}Store.rowToModel(result.data[0] as {{ENTITY_NAME}}Row);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getAllBy(filters: Partial<Pick<{{ENTITY_NAME}}Row, {{FILTERABLE_FIELDS}}>>): Promise<{{ENTITY_NAME}}[]> {
|
|
89
|
+
const filterKeys = Object.keys(filters);
|
|
90
|
+
|
|
91
|
+
// Runtime validation against SQL injection
|
|
92
|
+
for (const key of filterKeys) {
|
|
93
|
+
if (!{{ENTITY_NAME}}Store.FILTERABLE_FIELDS.includes(key)) {
|
|
94
|
+
throw new Error(\`Invalid filter field: \${key}\`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const whereConditions = filterKeys.map(key => \`\${key} = :\${key}\`);
|
|
99
|
+
|
|
100
|
+
if (whereConditions.length === 0) {
|
|
101
|
+
return this.getAll();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const query = \`SELECT * FROM {{TABLE_NAME}} WHERE \${whereConditions.join(' AND ')} AND deleted_at IS NULL ORDER BY created_at DESC\`;
|
|
105
|
+
const result = await this.db.query(query, filters);
|
|
106
|
+
|
|
107
|
+
if (!result.success) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result.data.map((row: {{ENTITY_NAME}}Row) => {{ENTITY_NAME}}Store.rowToModel(row));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async insert(model: Omit<{{ENTITY_NAME}}, 'id'>): Promise<{{ENTITY_NAME}}> {
|
|
115
|
+
try {
|
|
116
|
+
const row = {{ENTITY_NAME}}Store.modelToRow(model as {{ENTITY_NAME}});
|
|
117
|
+
delete row.id; // Remove id for insert
|
|
118
|
+
row.created_at = new Date();
|
|
119
|
+
row.updated_at = new Date();
|
|
120
|
+
|
|
121
|
+
const fields = Object.keys(row);
|
|
122
|
+
const placeholders = fields.map(field => \`:\${field}\`).join(', ');
|
|
123
|
+
|
|
124
|
+
const query = \`INSERT INTO {{TABLE_NAME}} (\${fields.join(', ')}) VALUES (\${placeholders})\`;
|
|
125
|
+
const result = await this.db.query(query, row);
|
|
126
|
+
|
|
127
|
+
if (!result.success || !result.insertId) {
|
|
128
|
+
throw new Error('Failed to insert {{ENTITY_NAME}}: Insert operation did not return a valid ID');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return this.getById(Number(result.insertId));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof MySQLQueryError) {
|
|
134
|
+
throw new Error(\`Failed to insert {{ENTITY_NAME}}: \${error.message}\`);
|
|
135
|
+
} else if (error instanceof MySQLConnectionError) {
|
|
136
|
+
throw new Error(\`Database connection error while inserting {{ENTITY_NAME}}: \${error.message}\`);
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async update(id: number, updates: Partial<Omit<{{ENTITY_NAME}}, 'id' | 'createdAt'>>): Promise<{{ENTITY_NAME}} | null> {
|
|
143
|
+
const existing = await this.getById(id);
|
|
144
|
+
if (!existing) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Extract only data properties, not methods
|
|
149
|
+
const updateData = this.extractDataProperties(updates);
|
|
150
|
+
const updateKeys = Object.keys(updateData);
|
|
151
|
+
|
|
152
|
+
// Runtime validation against SQL injection
|
|
153
|
+
for (const key of updateKeys) {
|
|
154
|
+
if (!{{ENTITY_NAME}}Store.UPDATABLE_FIELDS.includes(key)) {
|
|
155
|
+
throw new Error(\`Invalid update field: \${key}\`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const updateFields = updateKeys.map(key => \`\${key} = :\${key}\`);
|
|
160
|
+
|
|
161
|
+
if (updateFields.length === 0) {
|
|
162
|
+
return existing;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const params = { ...updateData, updated_at: new Date(), id };
|
|
166
|
+
|
|
167
|
+
const query = \`UPDATE {{TABLE_NAME}} SET \${updateFields.join(', ')}, updated_at = :updated_at WHERE id = :id\`;
|
|
168
|
+
await this.db.query(query, params);
|
|
169
|
+
|
|
170
|
+
return this.getById(id);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async upsert(model: Partial<{{ENTITY_NAME}}>): Promise<{{ENTITY_NAME}}> {
|
|
174
|
+
if (model.id) {
|
|
175
|
+
const existing = await this.getById(model.id);
|
|
176
|
+
if (existing) {
|
|
177
|
+
return this.update(model.id, model) as Promise<{{ENTITY_NAME}}>;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return this.insert(model as Omit<{{ENTITY_NAME}}, 'id'>);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async softDelete(id: number): Promise<boolean> {
|
|
185
|
+
const query = 'UPDATE {{TABLE_NAME}} SET deleted_at = :deleted_at WHERE id = :id AND deleted_at IS NULL';
|
|
186
|
+
const result = await this.db.query(query, { deleted_at: new Date(), id });
|
|
187
|
+
|
|
188
|
+
return result.success && (result.affectedRows || 0) > 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async count(filters?: Partial<Pick<{{ENTITY_NAME}}Row, {{FILTERABLE_FIELDS}}>>): Promise<number> {
|
|
192
|
+
let query = 'SELECT COUNT(*) as count FROM {{TABLE_NAME}} WHERE deleted_at IS NULL';
|
|
193
|
+
let params: Record<string, any> = {};
|
|
194
|
+
|
|
195
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
196
|
+
const whereConditions = Object.keys(filters).map(key => \`\${key} = :\${key}\`);
|
|
197
|
+
params = { ...filters };
|
|
198
|
+
query += \` AND \${whereConditions.join(' AND ')}\`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = await this.db.query(query, params);
|
|
202
|
+
|
|
203
|
+
if (!result.success || result.data.length === 0) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return result.data[0].count;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
{{CONVERSION_METHODS}}
|
|
211
|
+
}`,
|
|
212
|
+
conversionMethods: ` static rowToModel(row: {{ENTITY_NAME}}Row): {{ENTITY_NAME}} {
|
|
213
|
+
return new {{ENTITY_NAME}}(
|
|
214
|
+
row.id,
|
|
215
|
+
{{ROW_TO_MODEL_MAPPING}}
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static modelToRow(model: {{ENTITY_NAME}}): Partial<{{ENTITY_NAME}}Row> {
|
|
220
|
+
return {
|
|
221
|
+
id: model.id,
|
|
222
|
+
{{MODEL_TO_ROW_MAPPING}},
|
|
223
|
+
updated_at: new Date()
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private extractDataProperties(obj: any): Record<string, any> {
|
|
228
|
+
const result: Record<string, any> = {};
|
|
229
|
+
|
|
230
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
231
|
+
// Only include properties that are not functions and are in updatable fields
|
|
232
|
+
if (typeof value !== 'function' && {{ENTITY_NAME}}Store.UPDATABLE_FIELDS.includes(key)) {
|
|
233
|
+
result[key] = value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result;
|
|
238
|
+
}`
|
|
239
|
+
};
|
|
240
|
+
exports.fileTemplates = {
|
|
241
|
+
storeFile: `import { {{ENTITY_NAME}} } from '../../domain/entities/{{ENTITY_NAME}}';
|
|
242
|
+
import { StoreInterface } from '../interfaces/StoreInterface';
|
|
243
|
+
import { ProviderMysql, ISqlProvider, MySQLQueryError, MySQLConnectionError } from '@currentjs/provider-mysql';
|
|
244
|
+
|
|
245
|
+
{{ROW_INTERFACE}}
|
|
246
|
+
|
|
247
|
+
{{STORE_CLASS}}`,
|
|
248
|
+
storeInterface: `export interface StoreInterface<TModel, TRow> {
|
|
249
|
+
getById(id: number): Promise<TModel | null>;
|
|
250
|
+
getAll(page?: number, limit?: number): Promise<TModel[]>;
|
|
251
|
+
getAllByUserId(userId: number, page?: number, limit?: number): Promise<TModel[]>;
|
|
252
|
+
getBy(filters: Partial<TRow>): Promise<TModel | null>;
|
|
253
|
+
getAllBy(filters: Partial<TRow>): Promise<TModel[]>;
|
|
254
|
+
insert(model: Omit<TModel, 'id'>): Promise<TModel>;
|
|
255
|
+
update(id: number, updates: Partial<Omit<TModel, 'id' | 'createdAt'>>): Promise<TModel | null>;
|
|
256
|
+
upsert(model: Partial<TModel>): Promise<TModel>;
|
|
257
|
+
softDelete(id: number): Promise<boolean>;
|
|
258
|
+
count(filters?: Partial<TRow>): Promise<number>;
|
|
259
|
+
}`
|
|
260
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const validationTemplates: {
|
|
2
|
+
inputInterface: string;
|
|
3
|
+
validationFunction: string;
|
|
4
|
+
requiredStringValidation: string;
|
|
5
|
+
optionalStringValidation: string;
|
|
6
|
+
requiredNumberValidation: string;
|
|
7
|
+
optionalNumberValidation: string;
|
|
8
|
+
requiredBooleanValidation: string;
|
|
9
|
+
optionalBooleanValidation: string;
|
|
10
|
+
requiredDateValidation: string;
|
|
11
|
+
optionalDateValidation: string;
|
|
12
|
+
requiredComplexValidation: string;
|
|
13
|
+
optionalComplexValidation: string;
|
|
14
|
+
validationFileTemplate: string;
|
|
15
|
+
dtoInterface: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const typeMapping: {
|
|
18
|
+
string: string;
|
|
19
|
+
number: string;
|
|
20
|
+
boolean: string;
|
|
21
|
+
datetime: string;
|
|
22
|
+
json: string;
|
|
23
|
+
object: string;
|
|
24
|
+
array: string;
|
|
25
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.typeMapping = exports.validationTemplates = void 0;
|
|
4
|
+
exports.validationTemplates = {
|
|
5
|
+
inputInterface: `export interface {{INTERFACE_NAME}} {
|
|
6
|
+
{{INTERFACE_FIELDS}}
|
|
7
|
+
}`,
|
|
8
|
+
validationFunction: `export function {{FUNCTION_NAME}}(data: any): boolean {
|
|
9
|
+
const errors: string[] = [];
|
|
10
|
+
|
|
11
|
+
{{VALIDATION_LOGIC}}
|
|
12
|
+
|
|
13
|
+
if (errors.length > 0) {
|
|
14
|
+
throw new Error(\`Validation failed: \${errors.join(', ')}\`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return true;
|
|
18
|
+
}`,
|
|
19
|
+
requiredStringValidation: ` if (!data.{{FIELD_NAME}} || typeof data.{{FIELD_NAME}} !== 'string') {
|
|
20
|
+
errors.push('{{FIELD_NAME}} is required and must be a string');
|
|
21
|
+
}`,
|
|
22
|
+
optionalStringValidation: ` if (data.{{FIELD_NAME}} !== undefined && typeof data.{{FIELD_NAME}} !== 'string') {
|
|
23
|
+
errors.push('{{FIELD_NAME}} must be a string');
|
|
24
|
+
}`,
|
|
25
|
+
requiredNumberValidation: ` if (data.{{FIELD_NAME}} === undefined || data.{{FIELD_NAME}} === null || typeof data.{{FIELD_NAME}} !== 'number' || isNaN(data.{{FIELD_NAME}})) {
|
|
26
|
+
errors.push('{{FIELD_NAME}} is required and must be a number');
|
|
27
|
+
}`,
|
|
28
|
+
optionalNumberValidation: ` if (data.{{FIELD_NAME}} !== undefined && (typeof data.{{FIELD_NAME}} !== 'number' || isNaN(data.{{FIELD_NAME}}))) {
|
|
29
|
+
errors.push('{{FIELD_NAME}} must be a number');
|
|
30
|
+
}`,
|
|
31
|
+
requiredBooleanValidation: ` if (data.{{FIELD_NAME}} === undefined || data.{{FIELD_NAME}} === null || typeof data.{{FIELD_NAME}} !== 'boolean') {
|
|
32
|
+
errors.push('{{FIELD_NAME}} is required and must be a boolean');
|
|
33
|
+
}`,
|
|
34
|
+
optionalBooleanValidation: ` if (data.{{FIELD_NAME}} !== undefined && typeof data.{{FIELD_NAME}} !== 'boolean') {
|
|
35
|
+
errors.push('{{FIELD_NAME}} must be a boolean');
|
|
36
|
+
}`,
|
|
37
|
+
requiredDateValidation: ` if (!data.{{FIELD_NAME}} || !(data.{{FIELD_NAME}} instanceof Date) && isNaN(Date.parse(data.{{FIELD_NAME}}))) {
|
|
38
|
+
errors.push('{{FIELD_NAME}} is required and must be a valid date');
|
|
39
|
+
}`,
|
|
40
|
+
optionalDateValidation: ` if (data.{{FIELD_NAME}} !== undefined && !(data.{{FIELD_NAME}} instanceof Date) && isNaN(Date.parse(data.{{FIELD_NAME}}))) {
|
|
41
|
+
errors.push('{{FIELD_NAME}} must be a valid date');
|
|
42
|
+
}`,
|
|
43
|
+
// For complex types, just pass through for now
|
|
44
|
+
requiredComplexValidation: ` if (data.{{FIELD_NAME}} === undefined || data.{{FIELD_NAME}} === null) {
|
|
45
|
+
errors.push('{{FIELD_NAME}} is required');
|
|
46
|
+
}`,
|
|
47
|
+
optionalComplexValidation: ` // {{FIELD_NAME}} - complex type validation to be implemented later`,
|
|
48
|
+
validationFileTemplate: `// Generated validation for {{ENTITY_NAME}}
|
|
49
|
+
|
|
50
|
+
{{DTO_INTERFACES}}
|
|
51
|
+
|
|
52
|
+
{{VALIDATION_FUNCTIONS}}
|
|
53
|
+
`,
|
|
54
|
+
dtoInterface: `export interface {{DTO_NAME}} {
|
|
55
|
+
{{DTO_FIELDS}}
|
|
56
|
+
}`
|
|
57
|
+
};
|
|
58
|
+
exports.typeMapping = {
|
|
59
|
+
string: 'string',
|
|
60
|
+
number: 'number',
|
|
61
|
+
boolean: 'boolean',
|
|
62
|
+
datetime: 'Date',
|
|
63
|
+
json: 'any',
|
|
64
|
+
object: 'any',
|
|
65
|
+
array: 'any[]'
|
|
66
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type FieldConfig = {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required?: boolean;
|
|
5
|
+
auto?: boolean;
|
|
6
|
+
unique?: boolean;
|
|
7
|
+
enum?: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function toFileNameFromTemplateName(name: string): string;
|
|
10
|
+
export declare function renderListTemplate(entityName: string, templateName: string, basePath: string, fields: FieldConfig[], apiBase?: string): string;
|
|
11
|
+
export declare function renderDetailTemplate(entityName: string, templateName: string, fields: FieldConfig[]): string;
|
|
12
|
+
export declare function renderCreateTemplate(entityName: string, templateName: string, apiBase: string, fields: FieldConfig[], strategy?: string[], basePath?: string): string;
|
|
13
|
+
export declare function renderUpdateTemplate(entityName: string, templateName: string, apiBase: string, fields: FieldConfig[], strategy?: string[], basePath?: string): string;
|
|
14
|
+
export declare function renderDeleteTemplate(entityName: string, templateName: string, apiBase: string, strategy?: string[], basePath?: string): string;
|
|
15
|
+
export declare function renderLayoutTemplate(layoutName: string): string;
|
|
16
|
+
export {};
|