Files
seclusion/plop/utils/relation-parser.ts
charilezhou f126e03cf1 feat(plop): 代码生成器支持 CrudService 分层架构
- 新增 relation-parser.ts 关联关系 DSL 解析器
- 生成器支持三种服务类型选择:CrudService/RelationCrudService/ManyToManyCrudService
- 添加关联关系、多对多关系、统计关系配置问题
- 修复 helpers 导入路径扩展名问题

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:02:48 +08:00

217 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 关联关系 DSL 解析器
*
* DSL 格式:
* - 关联关系: 关联名:目标模型 字段1,字段2,... [noList]
* - 多对多: 关系名:中间表:外键:目标键:目标模型 字段1,字段2,...
*/
/**
* 关联关系配置
*/
export interface RelationConfig {
/** 关联名,如 'class' */
name: string;
/** 目标模型 PascalCase如 'Class' */
model: string;
/** 目标模型 camelCase如 'class' */
modelCamel: string;
/** select 字段列表 */
selectFields: string[];
/** 是否在列表中包含(默认 true */
includeInList: boolean;
}
/**
* 多对多关系<E585B3><E7B3BB><EFBFBD>
*/
export interface ManyToManyConfig {
/** 关系名,如 'teachers' */
name: string;
/** 中间表名,如 'classTeacher' */
through: string;
/** 当前实体外键,如 'classId' */
foreignKey: string;
/** 目标实体外键,如 'teacherId' */
targetKey: string;
/** 目标模型 camelCase如 'teacher' */
target: string;
/** 目标模型 PascalCase如 'Teacher' */
targetModel: string;
/** 目标实体 select 字段列表 */
selectFields: string[];
}
/**
* 解析关联关系 DSL
*
* @example
* 输入:
* ```
* class:Class id,code,name
* headTeacher:Teacher id,teacherNo,name,subject noList
* ```
*
* 输出:
* [
* { name: 'class', model: 'Class', selectFields: ['id', 'code', 'name'], includeInList: true },
* { name: 'headTeacher', model: 'Teacher', selectFields: ['id', 'teacherNo', 'name', 'subject'], includeInList: false },
* ]
*/
export function parseRelations(dsl: string): RelationConfig[] {
const lines = dsl
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
const relations: RelationConfig[] = [];
for (const line of lines) {
// 匹配格式: 关联名:目标模型 字段1,字段2,... [noList]
const match = line.match(/^(\w+):(\w+)\s+([\w,]+)(?:\s+(noList))?$/);
if (!match) {
console.warn(`无法解析关联配置行: ${line}`);
continue;
}
const [, name, model, fieldsStr, noListFlag] = match;
const selectFields = fieldsStr.split(',').map((f) => f.trim());
relations.push({
name,
model,
modelCamel: model.charAt(0).toLowerCase() + model.slice(1),
selectFields,
includeInList: !noListFlag,
});
}
return relations;
}
/**
* 解析多对多关系 DSL
*
* @example
* 输入:
* ```
* teachers:classTeacher:classId:teacherId:Teacher id,teacherNo,name,subject
* ```
*
* 输出:
* [
* {
* name: 'teachers',
* through: 'classTeacher',
* foreignKey: 'classId',
* targetKey: 'teacherId',
* target: 'teacher',
* targetModel: 'Teacher',
* selectFields: ['id', 'teacherNo', 'name', 'subject'],
* },
* ]
*/
export function parseManyToMany(dsl: string): ManyToManyConfig[] {
const lines = dsl
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
const configs: ManyToManyConfig[] = [];
for (const line of lines) {
// 匹配格式: 关系名:中间表:外键:目标键:目标模型 字段1,字段2,...
const match = line.match(/^(\w+):(\w+):(\w+):(\w+):(\w+)\s+([\w,]+)$/);
if (!match) {
console.warn(`无法解析多对多配置行: ${line}`);
continue;
}
const [, name, through, foreignKey, targetKey, targetModel, fieldsStr] = match;
const selectFields = fieldsStr.split(',').map((f) => f.trim());
configs.push({
name,
through,
foreignKey,
targetKey,
target: targetModel.charAt(0).toLowerCase() + targetModel.slice(1),
targetModel,
selectFields,
});
}
return configs;
}
/**
* 解析统计关系(逗号分隔的字符串)
*
* @example
* 输入: "students, orders"
* 输出: ['students', 'orders']
*/
export function parseCountRelations(input: string): string[] {
if (!input || !input.trim()) {
return [];
}
return input
.split(',')
.map((s) => s.trim())
.filter(Boolean);
}
/**
* 获取字段的 TypeScript 类型(用于生成关联类型)
* 简化版本,假设大多数字段是 string
*/
export function getFieldType(fieldName: string): string {
// 常见的非字符串字段
const numberFields = ['id', 'count', 'amount', 'price', 'quantity', 'sort', 'order'];
const booleanFields = ['is', 'has', 'can', 'should', 'enabled', 'active', 'visible'];
if (numberFields.some((f) => fieldName.toLowerCase().includes(f))) {
return 'number';
}
if (booleanFields.some((f) => fieldName.toLowerCase().startsWith(f))) {
return 'boolean';
}
return 'string';
}
/**
* 生成关联类型的 select 对象字符串
*
* @example
* 输入: ['id', 'code', 'name']
* 输出: '{ id: true, code: true, name: true }'
*/
export function generateSelectObject(fields: string[]): string {
const pairs = fields.map((f) => `${f}: true`);
return `{ ${pairs.join(', ')} }`;
}
/**
* 生成关联类型定义字符串
*
* @example
* 输入: { name: 'class', selectFields: ['id', 'code', 'name'] }
* 输出: 'class?: { id: string; code: string; name: string } | null'
*/
export function generateRelationTypeField(config: RelationConfig): string {
const fieldTypes = config.selectFields.map((f) => `${f}: ${getFieldType(f)}`);
return `${config.name}?: { ${fieldTypes.join('; ')} } | null`;
}
/**
* 生成多对多类型定义字符串
*
* @example
* 输入: { name: 'teachers', target: 'teacher', selectFields: ['id', 'name'] }
* 输出: 'teachers?: Array<{ teacher: { id: string; name: string } }>'
*/
export function generateManyToManyTypeField(config: ManyToManyConfig): string {
const fieldTypes = config.selectFields.map((f) => `${f}: ${getFieldType(f)}`);
return `${config.name}?: Array<{ ${config.target}: { ${fieldTypes.join('; ')} } }>`;
}