import PathLib from 'path'; import assert from 'assert'; import { writeFileSync, readdirSync, mkdirSync, fstat } from 'fs'; import { emptydirSync } from 'fs-extra'; import { assign, cloneDeep, difference, identity, intersection, keys, pull, uniq, uniqBy } from 'lodash'; import * as ts from 'typescript'; const { factory } = ts; import { ACTION_CONSTANT_IN_OAK_DOMAIN, TYPE_PATH_IN_OAK_DOMAIN, RESERVED_ENTITIES, STRING_LITERAL_MAX_LENGTH, NUMERICAL_LITERL_DEFAULT_PRECISION, NUMERICAL_LITERL_DEFAULT_SCALE, INT_LITERL_DEFAULT_WIDTH, LIB_OAK_DOMAIN, } from './env'; import { firstLetterLowerCase, firstLetterUpperCase } from '../utils/string'; const Schema: Record; fulltextIndex?: true; indexes?: ts.ArrayLiteralExpression; states: string[]; sourceFile: ts.SourceFile; locale: ts.ObjectLiteralExpression; // relationHierarchy?: ts.ObjectLiteralExpression; // reverseCascadeRelationHierarchy?: ts.ObjectLiteralExpression; toModi: boolean; actionType: string; static: boolean; inModi: boolean; hasRelationDef: false | ts.TypeAliasDeclaration; enumStringAttrs: string[], additionalImports: ts.ImportDeclaration[], }> = {}; const OneToMany: Record> = {}; const ManyToOne: Record> = {}; const ReversePointerEntities: Record = {}; const ReversePointerRelations: Record = {}; const ActionImportStatements = () => [ factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("ActionDef") )]) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN()}Action`), undefined ), factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([ factory.createImportSpecifier( false, undefined, factory.createIdentifier("GenericAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("AppendOnlyAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ReadOnlyAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ExcludeUpdateAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ExcludeRemoveAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("RelationAction") ), ]) ), factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), undefined ) ]; const ActionAsts: { [module: string]: { statements: Array; sourceFile: ts.SourceFile; importedFrom: Record; // actionNames: string[]; actionDefNames: string[]; }; } = {}; const SchemaAsts: { [module: string]: { statements: Array; sourceFile: ts.SourceFile; }; } = {}; function addRelationship(many: string, one: string, key: string, notNull: boolean) { const { [many]: manySet } = ManyToOne; const one2 = one === 'Schema' ? many : one; if (manySet) { manySet.push([one2, key, notNull]); } else { assign(ManyToOne, { [many]: [[one2, key, notNull]], }); } const { [one2]: oneSet } = OneToMany; if (oneSet) { oneSet.push([many, key, notNull]); } else { assign(OneToMany, { [one2]: [[many, key, notNull]], }); } } /** * 对relationship去重。一旦发生对象重定义这里就有可能重复 */ function uniqRelationships() { for (const entity in ManyToOne) { ManyToOne[entity] = uniqBy(ManyToOne[entity], (ele) => `${ele[0]}-${ele[1]}`); } for (const entity in OneToMany) { OneToMany[entity] = uniqBy(OneToMany[entity], (ele) => `${ele[0]}-${ele[1]}`); } for (const entity in ReversePointerRelations) { OneToMany[entity] = uniq(OneToMany[entity]); } } function createForeignRef(entity: string, foreignKey: string, ref: string) { if (entity === foreignKey) { return factory.createIdentifier(ref) } return factory.createQualifiedName( factory.createIdentifier(foreignKey), factory.createIdentifier(ref) ); } function pushStatementIntoActionAst( moduleName: string, node: ts.Statement, sourceFile: ts.SourceFile) { // let actionNames; let actionDefName; /* if (ts.isTypeAliasDeclaration(node) && node.name.text === 'ParticularAction') { const { type } = node; if (ts.isUnionTypeNode(type)) { actionNames = type.types.map( (ele) => { assert(ts.isTypeReferenceNode(ele)); const text = (ele.typeName).text; assert(text.endsWith('Action')); return firstLetterLowerCase(text.slice(0, text.length - 6)); } ) } else { assert(ts.isTypeReferenceNode(type)); const text = (type.typeName).text; assert(text.endsWith('Action')); actionNames = [firstLetterLowerCase(text.slice(0, text.length - 6))]; } } */ if (ts.isVariableStatement(node)) { const { declarationList: { declarations } } = node; declarations.forEach( (declaration) => { if (ts.isIdentifier(declaration.name) && declaration.name.text.endsWith('ActionDef')) { const { text } = declaration.name; actionDefName = firstLetterLowerCase(text.slice(0, text.length - 9)); } } ); } if (ActionAsts[moduleName]) { ActionAsts[moduleName].statements.push(node); /* if (actionNames) { ActionAsts[moduleName].actionNames = actionNames; } */ if (actionDefName) { ActionAsts[moduleName].actionDefNames.push(actionDefName); } } else { assign(ActionAsts, { [moduleName]: { statements: [...ActionImportStatements(), node], sourceFile, importedFrom: {}, // actionNames, actionDefNames: actionDefName ? [actionDefName] : [], } }); } } function pushStatementIntoSchemaAst(moduleName: string, statement: ts.Statement, sourceFile: ts.SourceFile) { if (SchemaAsts[moduleName]) { SchemaAsts[moduleName].statements.push(statement); } else { assign(SchemaAsts, { [moduleName]: { statements: [statement], sourceFile, } }); } } /** * 检查ActionDef是否满足合法的定义 * 1、ActionDef, Action, State三者命名是否一致 * @param actionDefNode */ function checkActionDefNameConsistent(filename: string, actionDefNode: ts.VariableDeclaration) { const { name, type } = actionDefNode; assert(ts.isTypeReferenceNode(type!)); const { typeArguments } = type!; assert(typeArguments!.length === 2); const [actionNode, stateNode] = typeArguments!; assert(ts.isIdentifier(name), `文件${filename}中的ActionDef${(name).text}不是一个有效的变量`); assert(name.text.endsWith('ActionDef'), `文件${filename}中的ActionDef${name.text}未以ActionDef结尾`); assert(ts.isTypeReferenceNode(actionNode) && ts.isTypeReferenceNode(stateNode), `文件${filename}中的ActionDef${name.text}类型声明中的action和state非法`); assert(ts.isIdentifier(actionNode.typeName) && ts.isIdentifier(stateNode.typeName)); assert(actionNode.typeName.text.endsWith('Action'), `文件${filename}中的ActionDef${name.text}所引用的Action${actionNode.typeName}未以Action结尾`); assert(stateNode.typeName.text.endsWith('State'), `文件${filename}中的ActionDef${name.text}所引用的Action${stateNode.typeName}未以Action结尾`); const adfName = name.text.slice(0, name.text.length - 9); const aName = actionNode.typeName.text.slice(0, actionNode.typeName.text.length - 6); const sName = stateNode.typeName.text.slice(0, stateNode.typeName.text.length - 5); assert(adfName === aName && aName === sName, `文件${filename}中的ActionDef${name.text}中ActionDef, Action和State的命名规则不一致`); } function addActionSource(moduleName: string, name: ts.Identifier, node: ts.ImportDeclaration) { const ast = ActionAsts[moduleName]; const { moduleSpecifier } = node; // todo 目前应该只会引用oak-domain/src/actions/action里的公共action,未来如果有交叉引用这里代码要修正(如果domain中也有引用action_constants这里应该也会错) assert(ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === ACTION_CONSTANT_IN_OAK_DOMAIN()); assign(ast.importedFrom, { [name.text]: node, }); } function getStringTextFromUnionStringLiterals(moduleName: string, filename: string, node: ts.TypeReferenceNode, program: ts.Program) { const checker = program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node.typeName); let declaration = symbol?.getDeclarations()![0]!; let isImport = false; /* const typee = checker.getDeclaredTypeOfSymbol(symbol!); const declaration = typee.aliasSymbol!.getDeclarations()![0]; */ if (ts.isImportSpecifier(declaration)) { isImport = true; const typee = checker.getDeclaredTypeOfSymbol(symbol!); declaration = typee.aliasSymbol!.getDeclarations()![0]; } assert(ts.isTypeAliasDeclaration(declaration)); const { type, name } = declaration; // assert(ts.isUnionTypeNode(type!) || ts.isLiteralTypeNode(type!), `${filename}中引用的action「${(name).text}」的定义不是union和stringLiteral类型`); // 如果这个action是从外部导入的,在这里要记下来此entity和这个导入之间的关系 if (isImport) { const importDeclartion = symbol!.getDeclarations()![0]!.parent.parent.parent; assert(ts.isImportDeclaration(importDeclartion)); addActionSource(moduleName, name, importDeclartion); } else { const ast = ActionAsts[moduleName]; assign(ast.importedFrom, { [name.text]: 'local', }); } const getStringLiteral = (ele: ts.TypeNode) => { assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), `${filename}中引用的action${(name).text}中存在不是stringliteral的类型`); assert(!ele.literal.text.includes('$'), `${filename}中引用的action${(name).text}中的action「${ele.literal.text}」包含非法字符$`); assert(ele.literal.text.length > 0, `${filename}中引用的action${(name).text}中的action「${ele.literal.text}」长度非法`); assert(ele.literal.text.length < STRING_LITERAL_MAX_LENGTH, `${filename}中引用的action${(name).text}中的action「${ele.literal.text}」长度过长`); return ele.literal.text; } if (ts.isUnionTypeNode(type)) { const actions = type.types!.map( ele => getStringLiteral(ele) ); return actions; } else { assert(ts.isLiteralTypeNode(type!), `${filename}中引用的action「${(name).text}」的定义不是union和stringLiteral类型`); const action = getStringLiteral(type); return [action]; } } const RESERVED_ACTION_NAMES = ['GenericAction', 'ParticularAction', 'ExcludeRemoveAction', 'ExcludeUpdateAction', 'ReadOnlyAction', 'AppendOnlyAction', 'RelationAction']; import { genericActions, relationActions } from '../actions/action'; import { unIndexedTypes } from '../types/DataType'; import { initinctiveAttributes } from '../types/Entity'; const OriginActionDict = { 'crud': 'GenericAction', 'excludeUpdate': 'ExcludeUpdateAction', 'excludeRemove': 'ExcludeRemoveAction', 'appendOnly': 'AppendOnlyAction', 'readOnly': 'ReadOnlyAction', }; function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode, program: ts.Program, sourceFile: ts.SourceFile, hasRelationDef?: boolean) { const actionTexts = genericActions.map( ele => ele ); if (hasRelationDef) { actionTexts.push(...relationActions); } if (ts.isUnionTypeNode(node)) { const actionNames = node.types.map( ele => { if (ts.isTypeReferenceNode(ele) && ts.isIdentifier(ele.typeName)) { return ele.typeName.text; } } ).filter( ele => !!ele ); assert(intersection(actionNames, RESERVED_ENTITIES).length === 0, `${filename}中的Action命名不能是「${RESERVED_ACTION_NAMES.join(',')}」之一`); node.types.forEach( ele => { if (ts.isTypeReferenceNode(ele)) { actionTexts.push(...getStringTextFromUnionStringLiterals(moduleName, filename, ele, program)); } else { assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), `【${moduleName}】action的定义既非Type也不是string`); actionTexts.push(ele.literal.text); } } ); } else if (ts.isTypeReferenceNode(node)) { if (ts.isIdentifier(node.typeName)) { assert(!RESERVED_ACTION_NAMES.includes(node.typeName.text), `${filename}中的Action命名不能是「${RESERVED_ACTION_NAMES.join(',')}」之一`); } actionTexts.push(...getStringTextFromUnionStringLiterals(moduleName, filename, node, program)); } else { assert(ts.isLiteralTypeNode(node) && ts.isStringLiteral(node.literal), `【${moduleName}】action的定义既非Type也不是string`); actionTexts.push(node.literal.text); } // 所有的action定义不能有重名 const ActionDict = {}; actionTexts.forEach( (action) => { assert(action.length <= STRING_LITERAL_MAX_LENGTH, `${filename}中的Action「${action}」命名长度大于${STRING_LITERAL_MAX_LENGTH}`); assert(/^[a-z][a-z|A-Z]+$/.test(action), `${filename}中的Action「${action}」命名不合法,必须以小字字母开头且只能包含字母`) if (ActionDict.hasOwnProperty(action)) { throw new Error(`文件${filename}中,Action定义上的【${action}】动作存在同名`); } else { assign(ActionDict, { [action]: 1, }); } } ); pushStatementIntoActionAst(moduleName, factory.createVariableStatement( [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList( [factory.createVariableDeclaration( factory.createIdentifier("actions"), undefined, undefined, factory.createArrayLiteralExpression( actionTexts.map( ele => factory.createStringLiteral(ele) ), false ) )], ts.NodeFlags.Const ) ), sourceFile ); } /** * entity的引用一定要以 import { Schema as XXX } from '..../XXX'这种形式 * @param declaration * @param filename * @returns */ function getEntityImported(declaration: ts.ImportDeclaration) { const { moduleSpecifier, importClause } = declaration; let entityImported: string | undefined; if (ts.isStringLiteral(moduleSpecifier)) { const { name: importedFileName } = PathLib.parse(moduleSpecifier.text); const { namedBindings } = importClause!; if (namedBindings && ts.isNamedImports(namedBindings)) { const { elements } = namedBindings; if (elements.find( ele => ts.isImportSpecifier(ele) && ele.name.text === importedFileName && ele.propertyName?.text === 'Schema' )) { entityImported = importedFileName; } } } return entityImported; } function checkLocaleEnumAttrs(node: ts.TypeLiteralNode, attrs: string[], filename: string) { const { members } = node; const memberKeys = members.map( (ele) => { assert(ts.isPropertySignature(ele) && ts.isIdentifier(ele.name)); return ele.name.text; } ); const lack = difference(attrs, memberKeys); if (lack.length > 0) { throw new Error(`${filename}中缺少了对${lack.join(',')}属性的locale定义`); } } function checkLocaleExpressionPropertyExists(root: ts.ObjectLiteralExpression, attr: string, exists: boolean, filename: string) { const { properties } = root; properties.forEach( (ele) => { assert(ts.isPropertyAssignment(ele) && (ts.isIdentifier(ele.name) || ts.isStringLiteral(ele.name)) && ts.isObjectLiteralExpression(ele.initializer)); const { properties: p2 } = ele.initializer; const pp = p2.find( (ele2) => { assert(ts.isPropertyAssignment(ele2) && ts.isIdentifier(ele2.name)); return ele2.name.text === attr; } ); if (exists && !pp) { throw new Error(`${filename}中的locale定义中的${ele.name.text}中缺少了${attr}的定义`); } else if (!exists && pp) { throw new Error(`${filename}中的locale定义中的${ele.name.text}中有多余的${attr}定义`); } } ) } function analyzeEntity(filename: string, path: string, program: ts.Program, relativePath?: string) { const fullPath = `${path}/${filename}`; const sourceFile = program.getSourceFile(fullPath); const moduleName = filename.split('.')[0]; if (Schema.hasOwnProperty(moduleName)) { delete ActionAsts[moduleName]; delete SchemaAsts[moduleName]; // removeFromRelationShip(moduleName); console.warn(`出现了同名的Entity定义「${moduleName}」,将使用${fullPath}取代掉默认对象,请检查新的对象结构及相关常量定义与原有的兼容,否则原有对象的相关逻辑会出现不可知异常`); } const referencedSchemas: string[] = []; const schemaAttrs: ts.TypeElement[] = []; let hasFulltextIndex: boolean = false; let indexes: ts.ArrayLiteralExpression; let beforeSchema = true; let hasActionDef = false; let hasRelationDef: boolean | ts.TypeAliasDeclaration = false; let hasActionOrStateDef = false; let toModi = false; let actionType = 'crud'; let _static = false; const enumStringAttrs: string[] = []; const states: string[] = []; const localEnumStringTypes: string[] = []; const additionalImports: ts.ImportDeclaration[] = []; let localeDef: ts.ObjectLiteralExpression | undefined = undefined; // let relationHierarchy: ts.ObjectLiteralExpression | undefined = undefined; // let reverseCascadeRelationHierarchy: ts.ObjectLiteralExpression | undefined = undefined; ts.forEachChild(sourceFile!, (node) => { if (ts.isImportDeclaration(node)) { const entityImported = getEntityImported(node); if (entityImported) { referencedSchemas.push(entityImported); } else if (!process.env.COMPLING_IN_DOMAIN && !relativePath?.startsWith(LIB_OAK_DOMAIN)) { /**import了domain以外的其它定义类型,需要被复制到生成的Schema文件中 * 这里必须注意,1、假设了domain当中定义的几个entity不会引用其它文件上的定义(除了type里的那些通用类型,默认都会被输出到文件中) * 2、假设了其它项目文件不会引用domain当中除了type通用类型之外的其它内容,否则不会被输出到文件中 * 这里主要是对import的处理比较粗略,日后有需要的时候再精修 */ const { moduleSpecifier, importClause } = node; if (ts.isStringLiteral(moduleSpecifier) && !moduleSpecifier.text.startsWith(LIB_OAK_DOMAIN)) { // 编译后的路径默认要深一层 const moduleSpecifier2Text = relativePath ? PathLib.join( relativePath, moduleSpecifier.text ).replace(/\\/g, '/') : PathLib.join('..', moduleSpecifier.text).replace( /\\/g, '/' ); additionalImports.push( factory.updateImportDeclaration( node, undefined, undefined, importClause, factory.createStringLiteral(moduleSpecifier2Text), undefined ) ); } } } if (ts.isInterfaceDeclaration(node)) { // schema 定义 if (node.name.text === 'Schema') { assert(!localeDef, `【${filename}】locale定义须在Schema之后`); let hasEntityAttr = false; let hasEntityIdAttr = false; const { members, heritageClauses } = node; assert(['EntityShape'].includes((heritageClauses![0].types![0].expression).text), moduleName); members.forEach( (attrNode) => { const { type, name, questionToken } = attrNode; const attrName = (name).text; if (ts.isTypeReferenceNode(type!) && ts.isIdentifier(type.typeName)) { if ((referencedSchemas.includes(type.typeName.text) || type.typeName.text === 'Schema')) { addRelationship(moduleName, type.typeName.text, attrName, !!questionToken); schemaAttrs.push(attrNode); } else if (type.typeName.text === 'Array') { // 这是一对多的反向指针的引用,需要特殊处理 const { typeArguments } = type; assert(typeArguments!.length === 1 && ts.isTypeReferenceNode(typeArguments![0]) && ts.isIdentifier(typeArguments![0].typeName) && referencedSchemas.includes(typeArguments![0].typeName.text), `「${filename}」非法的属性定义「${attrName}」`); const reverseEntity = typeArguments![0].typeName.text; if (ReversePointerRelations[reverseEntity]) { if (!ReversePointerRelations[reverseEntity].includes(moduleName)) { ReversePointerRelations[reverseEntity].push(moduleName); } } else { assign(ReversePointerRelations, { [reverseEntity]: [moduleName], }); } if (reverseEntity === 'Modi') { toModi = true; } } else { schemaAttrs.push(attrNode); if (localEnumStringTypes.includes(type.typeName.text)) { enumStringAttrs.push((name).text); } } } else if (ts.isArrayTypeNode(type!) && ts.isTypeReferenceNode(type.elementType) && ts.isIdentifier(type.elementType.typeName)) { const { typeName } = type.elementType; if (referencedSchemas.includes(typeName.text)) { // 这也是一对多的反指定义 const reverseEntity = typeName.text; if (ReversePointerRelations[reverseEntity]) { ReversePointerRelations[reverseEntity].push(moduleName); } else { assign(ReversePointerRelations, { [reverseEntity]: [moduleName], }); } if (reverseEntity === 'Modi') { toModi = true; } } else { throw new Error(`对象${moduleName}中定义的属性${attrName}是不可识别的数组类别`); } } else { schemaAttrs.push(attrNode); if (ts.isUnionTypeNode(type!)) { const { types } = type; if (ts.isLiteralTypeNode(types[0]) && ts.isStringLiteral(types[0].literal)) { enumStringAttrs.push((name).text); types.forEach( (ele) => { assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), `「${filename}」不支持混合型的枚举属性定义「${attrName}」`); assert(ele.literal.text.length < STRING_LITERAL_MAX_LENGTH, `「${filename}」中定义的属性枚举「${attrName}」的字符串长度应小于${STRING_LITERAL_MAX_LENGTH}`); } ) } } } if (attrName === 'entity') { assert(ts.isTypeReferenceNode(type!) && ts.isIdentifier(type.typeName), `「${moduleName}」中entity属性的定义不是String<32>类型,entity是系统用于表示反指指针的保留属性,请勿他用`); const { typeArguments } = type; assert(type.typeName.text === 'String' && typeArguments && typeArguments.length === 1, `「${moduleName}」中entity属性的定义不是String<32>类型,entity是系统用于表示反指指针的保留属性,请勿他用`); const [node] = typeArguments; if (ts.isLiteralTypeNode(node) && ts.isNumericLiteral(node.literal)) { if (parseInt(node.literal.text) > 32) { assert(false, `「${moduleName}」中entity属性的定义不是String<32>类型,entity是系统用于表示反指指针的保留属性,请勿他用`); } else { hasEntityAttr = true; } } } if (attrName === 'entityId') { assert(ts.isTypeReferenceNode(type!) && ts.isIdentifier(type.typeName), `「${moduleName}」中entityId属性的定义不是String<64>类型,entityId是系统用于表示反指指针的保留属性,请勿他用`); const { typeArguments } = type; assert(type.typeName.text === 'String' && typeArguments && typeArguments.length === 1, `「${moduleName}」中entityId属性的定义不是String<64>类型,entityId是系统用于表示反指指针的保留属性,请勿他用`); const [node] = typeArguments; if (ts.isLiteralTypeNode(node) && ts.isNumericLiteral(node.literal)) { if (parseInt(node.literal.text) !== 64) { assert(false, `「${moduleName}」中entityId属性的定义不是String<64>类型,entityId是系统用于表示反指指针的保留属性,请勿他用`); } else { hasEntityIdAttr = true; } } } } ); if (hasEntityAttr && hasEntityIdAttr) { assign(ReversePointerEntities, { [moduleName]: 1, }); } beforeSchema = false; // 对于不是Oper的对象,全部建立和OperEntity的反指关系 if (!['Oper', 'OperEntity', 'ModiEntity'].includes(moduleName)) { if (ReversePointerRelations['OperEntity'] && !ReversePointerRelations['OperEntity'].includes(moduleName)) { ReversePointerRelations['OperEntity'].push(moduleName); } else { assign(ReversePointerRelations, { ['OperEntity']: [moduleName], }); } // 对于不是Modi的对象,全部建立和ModiEntity的反指关系 if (!['Modi'].includes(moduleName) && !toModi) { if (ReversePointerRelations['ModiEntity'] && !ReversePointerRelations['ModiEntity'].includes(moduleName)) { ReversePointerRelations['ModiEntity'].push(moduleName); } else { assign(ReversePointerRelations, { ['ModiEntity']: [moduleName], }); } } } } else if (beforeSchema) { // 本地规定的一些形状定义,直接使用 pushStatementIntoSchemaAst(moduleName, node, sourceFile!); } } if (ts.isTypeAliasDeclaration(node)) { // action 定义 if (node.name.text === 'Action') { assert(!localeDef, `【${filename}】locale定义须在Action之后`); hasActionDef = true; const modifiers = [factory.createModifier(ts.SyntaxKind.ExportKeyword)]; pushStatementIntoActionAst( moduleName, factory.updateTypeAliasDeclaration( node, node.decorators, modifiers, factory.createIdentifier('ParticularAction'), node.typeParameters, node.type ), sourceFile! ); const actionDefNodes = [ factory.createTypeReferenceNode( OriginActionDict[actionType as keyof typeof OriginActionDict], undefined ), factory.createTypeReferenceNode( 'ParticularAction', undefined ) ] as ts.TypeNode[]; if (hasRelationDef || moduleName === 'User') { actionDefNodes.push( factory.createTypeReferenceNode( 'RelationAction', undefined ) ); } if (process.env.COMPLING_AS_LIB) { actionDefNodes.push( factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ); } pushStatementIntoActionAst( moduleName, factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Action"), undefined, factory.createUnionTypeNode(actionDefNodes) ), sourceFile! ); dealWithActions(moduleName, filename, node.type, program, sourceFile!, !!hasRelationDef || moduleName === 'User'); } else if (node.name.text === 'Relation') { assert(!hasActionDef, `【${filename}】action定义须在Relation之后`); assert(!localeDef, `【${filename}】locale定义须在Relation之后`); // 增加userXXX对象的描述 if (ts.isLiteralTypeNode(node.type)) { assert(ts.isStringLiteral(node.type.literal)); assert(node.type.literal.text.length < STRING_LITERAL_MAX_LENGTH, `Relation定义的字符串长度不长于${STRING_LITERAL_MAX_LENGTH}(${filename},${node.type.literal.text})`); } else { assert(ts.isUnionTypeNode(node.type), `Relation的定义只能是string类型(${filename})`); node.type.types.forEach( (ele) => { assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), `Relation的定义只能是string类型(${filename})`); assert(ele.literal.text.length < STRING_LITERAL_MAX_LENGTH, `Relation定义的字符串长度不长于${STRING_LITERAL_MAX_LENGTH}(${filename},${ele.literal.text})`); } ); } const entityLc = firstLetterLowerCase(moduleName); const relationEntityName = `User${moduleName}`; const relationSchemaAttrs: ts.TypeElement[] = [ factory.createPropertySignature( undefined, factory.createIdentifier("user"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("User"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier(entityLc), undefined, factory.createTypeReferenceNode( factory.createIdentifier(moduleName), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier('relation'), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Relation"), undefined ) ), ]; assign(Schema, { [relationEntityName]: { schemaAttrs: relationSchemaAttrs, sourceFile, enumStringAttrs: ['relation'], actionType: 'excludeUpdate', additionalImports: [ factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("Relation") )]) ), factory.createStringLiteral(`../${moduleName}/Schema`), undefined ) ], }, }); addRelationship(relationEntityName, 'User', 'user', true); addRelationship(relationEntityName, moduleName, entityLc, true); // 对UserEntityGrant对象,建立相应的反指关系 if (ReversePointerRelations['UserEntityGrant']) { if (!ReversePointerRelations['UserEntityGrant'].includes(moduleName)) { ReversePointerRelations['UserEntityGrant'].push(moduleName); } } else { assign(ReversePointerRelations, { ['UserEntityGrant']: [moduleName], }); } hasRelationDef = node; } else if (node.name.text.endsWith('Action') || node.name.text.endsWith('State')) { assert(!localeDef, `【${filename}】locale定义须在Action/State之后`); hasActionOrStateDef = true; const { type } = node; if (ts.isUnionTypeNode(type)) { pushStatementIntoActionAst(moduleName, factory.updateTypeAliasDeclaration( node, node.decorators, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], node.name, node.typeParameters, process.env.COMPLING_AS_LIB ? factory.createUnionTypeNode([ ...type.types, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ]) : type ), sourceFile!); } else { assert(ts.isLiteralTypeNode(type) || ts.isTypeReferenceNode(type), `${moduleName} - ${node.name}`); pushStatementIntoActionAst(moduleName, factory.updateTypeAliasDeclaration( node, node.decorators, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], node.name, node.typeParameters, process.env.COMPLING_AS_LIB ? factory.createUnionTypeNode([ type, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ]) : type ), sourceFile!); } } else if (beforeSchema) { // 本地规定的一些形状定义,直接使用 pushStatementIntoSchemaAst(moduleName, node, sourceFile!); if (ts.isUnionTypeNode(node.type) && ts.isLiteralTypeNode(node.type.types[0]) && ts.isStringLiteral(node.type.types[0].literal)) { // 本文件内定义的枚举类型 localEnumStringTypes.push(node.name.text); node.type.types.forEach( (ele) => { assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), `「${filename}」不支持混合型的常量定义「${node.name.text}」`); assert(ele.literal.text.length < STRING_LITERAL_MAX_LENGTH, `「${filename}」中定义的常量枚举「${node.name.text}」的字符串长度应小于${STRING_LITERAL_MAX_LENGTH}`); } ) } } } if (ts.isVariableStatement(node)) { const { declarationList: { declarations } } = node; declarations.forEach( (declaration) => { if (ts.isIdentifier(declaration.name) && declaration.name.text.endsWith('ActionDef')) { if (declaration.type && ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ActionDef') { // 是显示的actionDef定义 checkActionDefNameConsistent(filename, declaration); const { typeArguments } = declaration.type; assert(typeArguments!.length === 2); const [actionNode, stateNode] = typeArguments!; const checker = program.getTypeChecker(); let symbol = checker.getSymbolAtLocation((actionNode).typeName); let declaration2 = symbol!.getDeclarations()![0]; if (declaration2.getSourceFile() === sourceFile) { // pushStatementIntoActionAst(moduleName, declaration2, sourceFile); } symbol = checker.getSymbolAtLocation((stateNode).typeName); declaration2 = symbol!.getDeclarations()![0]; if (declaration2.getSourceFile() === sourceFile) { // 检查state的定义合法 assert(ts.isTypeAliasDeclaration(declaration2) && ts.isUnionTypeNode(declaration2.type), `「${filename}」State「${(declaration2).name}」的定义只能是或结点`); declaration2.type.types.forEach( (type) => { assert(ts.isLiteralTypeNode(type) && ts.isStringLiteral(type.literal), `「${filename}」State「${(declaration2).name}」的定义只能是字符串`); assert(type.literal.text.length < STRING_LITERAL_MAX_LENGTH, `「${filename}」State「${type.literal.text}」的长度大于「${STRING_LITERAL_MAX_LENGTH}」`); } ); /* pushStatementIntoActionAst(moduleName, factory.updateTypeAliasDeclaration( declaration2, declaration2.decorators, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], declaration2.name, declaration2.typeParameters, declaration2.type ), sourceFile); */ } } pushStatementIntoActionAst(moduleName, node, sourceFile!); const adName = declaration.name.text.slice(0, declaration.name.text.length - 9); const attr = adName.concat('State'); schemaAttrs.push( factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(attr)), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( factory.createIdentifier(attr), ) ) ); states.push(firstLetterLowerCase(attr)); } else if (declaration.type && (ts.isArrayTypeNode(declaration.type!) && ts.isTypeReferenceNode(declaration.type.elementType) && ts.isIdentifier(declaration.type.elementType.typeName) && declaration.type.elementType.typeName.text === 'Index' || ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'Array' && ts.isTypeReferenceNode(declaration.type.typeArguments![0]) && ts.isIdentifier(declaration.type.typeArguments![0].typeName) && declaration.type.typeArguments![0].typeName.text === 'Index')) { // 对索引Index的定义 const indexNameDict: Record = {}; assert(ts.isArrayLiteralExpression(declaration.initializer!), `「${filename}」Index「${declaration.name.getText()}」的定义必须符合规范`); // todo 这里应该先做一个类型检查的,但不知道怎么写 by Xc // 检查索引的属性是否合法 const { elements } = declaration.initializer; elements.forEach( (ele) => { let isFulltextIndex = false; assert(ts.isObjectLiteralExpression(ele)); const { properties } = ele; const attrProperty = properties.find( (ele2) => { assert(ts.isPropertyAssignment(ele2)); return ele2.name.getText() === 'attributes'; } ) as ts.PropertyAssignment; assert(ts.isArrayLiteralExpression(attrProperty.initializer)); const nameProperty = properties.find( (ele2) => { assert(ts.isPropertyAssignment(ele2)); return ele2.name.getText() === 'name'; } ) as ts.PropertyAssignment; assert(ts.isStringLiteral(nameProperty.initializer)); const nameText = nameProperty.initializer.text; if (indexNameDict[nameText]) { throw new Error(`「${filename}」索引定义重名「${nameText}」`); } assign(indexNameDict, { [nameText]: true, }); const configProperty = properties.find( (ele2) => { assert(ts.isPropertyAssignment(ele2)); return ele2.name.getText() === 'config'; } ) as ts.PropertyAssignment; if (configProperty) { assert(ts.isObjectLiteralExpression(configProperty.initializer)); const { properties: properties2 } = configProperty.initializer; const typeProperty = properties2.find( (ele2) => { assert(ts.isPropertyAssignment(ele2)); return ele2.name.getText() === 'type'; } ) as ts.PropertyAssignment; if (typeProperty && (typeProperty.initializer!).text === 'fulltext') { // 定义了全文索引 if (hasFulltextIndex) { throw new Error(`「${filename}」只能定义一个全文索引`); } hasFulltextIndex = true; isFulltextIndex = true; } } const { elements } = attrProperty.initializer; // 每个属性都应该在schema中有值,且对象类型是可索引值 elements.forEach( (ele2) => { assert(ts.isObjectLiteralExpression(ele2)); const { properties: properties2 } = ele2; const nameProperty = properties2.find( (ele3) => { assert(ts.isPropertyAssignment(ele3)); return ele3.name.getText() === 'name'; } ) as ts.PropertyAssignment; const indexAttrName = (nameProperty.initializer!).text; if (!initinctiveAttributes.includes(indexAttrName)) { const schemaNode = schemaAttrs.find( (ele3) => { assert(ts.isPropertySignature(ele3)); return (ele3.name).text === indexAttrName; } ) as ts.PropertySignature; if (!schemaNode) { throw new Error(`「${filename}」中索引「${nameText}」的属性「${indexAttrName}」定义非法`); } const { type, name } = schemaNode; const entity = moduleName; const { [entity]: manyToOneSet } = ManyToOne; if (ts.isTypeReferenceNode(type!)) { const { typeName } = type; if (ts.isIdentifier(typeName)) { const { text } = typeName; const text2 = text === 'Schema' ? entity : text; const manyToOneItem = manyToOneSet && manyToOneSet.find( ([refEntity, attrName]) => refEntity === text2 && attrName === (name).text ); if (!manyToOneItem) { // 如果不是外键,则不能是Text, File if (isFulltextIndex) { assert(['Text', 'String'].includes(text2), `「${filename}」中全文索引「${nameText}」定义的属性「${indexAttrName}」类型非法,只能是Text/String`); } else { assert(!unIndexedTypes.includes(text2), `「${filename}」中索引「${nameText}」的属性「${indexAttrName}」的类型为「${text2}」,不可索引`); } } else { assert(!isFulltextIndex, `「${filename}」中全文索引「${nameText}」的属性「${indexAttrName}」类型非法,只能为Text/String`); // 在这里把外键加上Id,这样storageSchema才能正常通过 // 这里的写法不太好,未来TS版本高了可能会有问题。by Xc 20230131 Object.assign(nameProperty, { initializer: factory.createStringLiteral(`${indexAttrName}Id`), }); } } else { assert(false); // 这是什么case,不确定 } } else { assert(!isFulltextIndex, `「${filename}」中全文索引「${nameText}」的属性「${indexAttrName}」类型只能为Text/String`); assert(ts.isUnionTypeNode(type!) || ts.isLiteralTypeNode(type!), `${entity}中索引「${nameText}」的属性${(name).text}有定义非法`); } } } ); } ); indexes = declaration.initializer; } else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { // locale定义 const { type, initializer } = declaration; assert(ts.isObjectLiteralExpression(initializer!)); const { properties } = initializer; assert(properties.length > 0, `${filename}至少需要有一种locale定义`); const allEnumStringAttrs = enumStringAttrs.concat(states); const { typeArguments } = type; assert(typeArguments && ts.isTypeReferenceNode(typeArguments[0]) && ts.isIdentifier(typeArguments[0].typeName) && typeArguments[0].typeName.text === 'Schema', `${filename}中缺少locale定义,或者locale类型定义的第一个参数不是Schema`); if (hasActionDef) { assert(ts.isTypeReferenceNode(typeArguments[1]) && ts.isIdentifier(typeArguments[1].typeName) && typeArguments[1].typeName.text === 'Action', `${filename}中locale类型定义的第二个参数不是Action`); // 检查每种locale定义中都应该有'action'域 checkLocaleExpressionPropertyExists(initializer, 'action', true, filename); } else { assert(ts.isLiteralTypeNode(typeArguments[1]) && ts.isStringLiteral(typeArguments[1].literal), `${filename}中locale类型定义的第二个参数不是字符串`); checkLocaleExpressionPropertyExists(initializer, 'action', false, filename); } if (hasRelationDef) { assert(ts.isTypeReferenceNode(typeArguments[2]) && ts.isIdentifier(typeArguments[2].typeName) && typeArguments[2].typeName.text === 'Relation', `${filename}中的locale类型定义的第三个参数不是Relation`); // 检查每种locale定义中都应该有'r'域 checkLocaleExpressionPropertyExists(initializer, 'r', true, filename); } else { assert(ts.isLiteralTypeNode(typeArguments[2]) && ts.isStringLiteral(typeArguments[2].literal), `${filename}中locale类型定义的第三个参数不是空字符串`); checkLocaleExpressionPropertyExists(initializer, 'r', false, filename); } if (allEnumStringAttrs.length > 0) { assert(ts.isTypeLiteralNode(typeArguments[3]), `${filename}中的locale类型定义的第四个参数不是{}`); checkLocaleEnumAttrs(typeArguments[3], allEnumStringAttrs, filename); // 检查每种locale定义中都应该有'v'域 checkLocaleExpressionPropertyExists(initializer, 'v', true, filename); } else { assert(ts.isTypeLiteralNode(typeArguments[3]), `${filename}中的locale类型定义的第四个参数不是{}`); assert(typeArguments[3].members.length == 0, `${filename}中locale类型定义的第四个参数不应存在相应的v定义`) // 检查每种locale定义中都应该有'v'域 checkLocaleExpressionPropertyExists(initializer, 'v', false, filename); } localeDef = initializer; } else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'Configuration') { assert(!hasActionDef, `${moduleName}中的Configuration定义在Action之后`); assert(ts.isObjectLiteralExpression(declaration.initializer!)); const { properties } = declaration.initializer; const atProperty = properties.find( ele => ts.isPropertyAssignment(ele) && ts.isIdentifier(ele.name) && ele.name.text === 'actionType' ); const staticProperty = properties.find( ele => ts.isPropertyAssignment(ele) && ts.isIdentifier(ele.name) && ele.name.text === 'static' ); if (atProperty) { actionType = ((atProperty).initializer).text; } if (staticProperty) { _static = true; // static如果有值只能为true } } /* else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'RelationHierarchy') { // RelationHierary assert(hasRelationDef, `${moduleName}中的Relation定义在RelationHierarchy之后`); const { initializer } = declaration; assert(ts.isObjectLiteralExpression(initializer!), `${moduleName}中的RelationHierarchy的定义必须是初始化为ObjectLiteralExpress`); relationHierarchy = initializer; } else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ReverseCascadeRelationHierarchy') { // ReverseCascadeRelationHierarchy assert(hasRelationDef, `${moduleName}中的Relation定义在ReverseCascadeRelationHierarchy之后`); const { initializer } = declaration; assert(ts.isObjectLiteralExpression(initializer!), `${moduleName}中的RelationHierarchy的定义必须是初始化为ObjectLiteralExpress`); reverseCascadeRelationHierarchy = initializer; } */ else { throw new Error(`${moduleName}:不能理解的定义内容${(declaration.name as ts.Identifier).text}`); } } ); } }); if (!hasActionDef && hasActionOrStateDef) { throw new Error(`${filename}中有Action或State定义,但没有定义完整的Action类型`); } if (hasActionDef && actionType !== 'crud') { throw new Error(`${filename}中有Action定义,但却定义了actionType不是crud`); } assert(schemaAttrs.length > 0, `对象${moduleName}没有任何属性定义`); const schema = { schemaAttrs, sourceFile, toModi, actionType, static: _static, hasRelationDef, enumStringAttrs: enumStringAttrs.concat(states), additionalImports, }; if (hasFulltextIndex) { assign(schema, { fulltextIndex: true, }); } if (indexes!) { assign(schema, { indexes, }); } if (!localeDef) { throw new Error(`${filename}中缺少了locale定义`); } else { assign(schema, { locale: localeDef, }); } /* if (hasRelationDef) { if(!relationHierarchy && !reverseCascadeRelationHierarchy){ console.warn(`${filename}中定义了Relation,但并没有relationHierarchy或reverseCascadeRelationHierarchy的定义,请注意自主编写权限分配的checker`); } if (relationHierarchy) { assign(schema, { relationHierarchy, }); } if (reverseCascadeRelationHierarchy) { assign(schema, { reverseCascadeRelationHierarchy, }); } } else { assert(!relationHierarchy, `${filename}中具有relationHierarchy定义但没有Relation定义`); assert(!reverseCascadeRelationHierarchy, `${filename}中具有reverseCascadeRelationHierarchy定义但没有Relation定义`) } */ assign(Schema, { [moduleName]: schema, }); } /** * 生成Schema * @param statements * @param schemaAttrs * @param entity */ function constructSchema(statements: Array, entity: string) { const { schemaAttrs } = Schema[entity]; const members: Array = [ ]; const members2: Array = []; const { [entity]: manyToOneSet } = ManyToOne; const { [entity]: oneToManySet } = OneToMany; const referenceEntities: string[] = []; for (const attr of schemaAttrs) { const { type, name, questionToken } = attr; const attrName = (name).text; if (ts.isTypeReferenceNode(type!)) { const { typeName } = type; if (ts.isIdentifier(typeName)) { const { text } = typeName; const text2 = text === 'Schema' ? entity : text; const manyToOneItem = manyToOneSet && manyToOneSet.find( ([refEntity, attrName]) => refEntity === text2 && attrName === attrName ); if (manyToOneItem) { referenceEntities.push(text2); members2.push( factory.createPropertySignature( undefined, name, questionToken, questionToken ? factory.createUnionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, text2, 'Schema') ), factory.createLiteralTypeNode(factory.createNull()) ]) : factory.createTypeReferenceNode( createForeignRef(entity, text2, 'Schema') ) ) ); const foreignKey = `${attrName}Id`; members.push( factory.createPropertySignature( undefined, factory.createIdentifier(foreignKey), questionToken, questionToken ? factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier('ForeignKey'), [ factory.createLiteralTypeNode( factory.createStringLiteral(firstLetterLowerCase(text2)) ) ] ), factory.createLiteralTypeNode(factory.createNull()) ]) : factory.createTypeReferenceNode( factory.createIdentifier('ForeignKey'), [ factory.createLiteralTypeNode( factory.createStringLiteral(firstLetterLowerCase(text2)) ) ] ) ) ); } else { // assert(types.includes(text), `${entity}中的属性${name.toString()}有非法的属性类型定义`); // 处理entity这种特殊情况 if (ReversePointerRelations[entity] && attrName === 'entity') { const entityUnionTypeNode: ts.TypeNode[] = ReversePointerRelations[entity].map( ele => factory.createLiteralTypeNode( factory.createStringLiteral(firstLetterLowerCase(ele)) ) ); if (process.env.COMPLING_AS_LIB) { // 如果是建立 base-domain,还要容纳可能的其它对象引用 entityUnionTypeNode.push( factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ) } members.push( factory.createPropertySignature( undefined, name, questionToken, questionToken ? factory.createUnionTypeNode([ factory.createUnionTypeNode( entityUnionTypeNode ), factory.createLiteralTypeNode(factory.createNull()) ]) : factory.createUnionTypeNode( entityUnionTypeNode ) ) ); } else { members.push( factory.createPropertySignature( undefined, name, questionToken, questionToken ? factory.createUnionTypeNode([ type, factory.createLiteralTypeNode(factory.createNull()) ]) : type ) ); } } } else { assert(false); // 这是什么case,不确定 members.push(attr); } } else { assert(ts.isUnionTypeNode(type!) || ts.isLiteralTypeNode(type!), `${entity}有非法的属性类型定义${(name).text}`); members.push( factory.createPropertySignature( undefined, name, questionToken, questionToken ? factory.createUnionTypeNode([ type, factory.createLiteralTypeNode(factory.createNull()) ]) : type ) ); } } // 处理reverserPointer const reverseOnes = ReversePointerRelations[entity]; if (reverseOnes) { reverseOnes.forEach( (one) => { referenceEntities.push(one); members2.push( factory.createPropertySignature( undefined, firstLetterLowerCase(one), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( createForeignRef(entity, one, 'Schema') ) ) ); } ) } const foreignKeySet: Record = {}; if (oneToManySet) { for (const oneToManyItem of oneToManySet) { const [entityName, foreignKey] = oneToManyItem; if (referenceEntities.indexOf(entityName) === -1) { referenceEntities.push(entityName); } if (foreignKeySet.hasOwnProperty(entityName)) { foreignKeySet[entityName].push(foreignKey); } else { foreignKeySet[entityName] = [foreignKey]; } } for (const entityName in foreignKeySet) { const entityNameLc = firstLetterLowerCase(entityName); foreignKeySet[entityName].forEach( (foreignKey) => { const identifier = `${entityNameLc}$${foreignKey}`; members2.push( factory.createPropertySignature( undefined, identifier, factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'Schema'), undefined )] ), ) ); const aggrIdentifier = `${entityNameLc}$${foreignKey}$$aggr`; members2.push( factory.createPropertySignature( undefined, aggrIdentifier, factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( factory.createIdentifier("AggregationResult"), [factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'Schema'), undefined )] ) ) ) } ); } } uniq(referenceEntities).forEach( (ele) => { if (ele !== entity) { statements.push(factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamespaceImport( factory.createIdentifier(ele) ) ), factory.createStringLiteral(`../${ele}/Schema`) )); } } ); // 在这里把需要直接拷贝过来的语句写入 if (SchemaAsts[entity]) { statements.push(...SchemaAsts[entity].statements); } statements.push( factory.createTypeAliasDeclaration( undefined, [ factory.createModifier(ts.SyntaxKind.ExportKeyword) ], factory.createIdentifier('OpSchema'), undefined, factory.createIntersectionTypeNode([ factory.createTypeReferenceNode('EntityShape'), factory.createTypeLiteralNode(members) ]) ), factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("OpAttr"), undefined, factory.createTypeOperatorNode( ts.SyntaxKind.KeyOfKeyword, factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ) ) ) ); statements.push( factory.createTypeAliasDeclaration( undefined, [ factory.createModifier(ts.SyntaxKind.ExportKeyword) ], factory.createIdentifier('Schema'), undefined, factory.createIntersectionTypeNode( [ factory.createTypeReferenceNode('EntityShape'), factory.createTypeLiteralNode(members.concat(members2)), factory.createMappedTypeNode( undefined, factory.createTypeParameterDeclaration( undefined, factory.createIdentifier("A"), factory.createTypeReferenceNode( factory.createIdentifier("ExpressionKey"), undefined ), undefined ), undefined, factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), undefined ) ] ) ) ); } /** * 生成Query * @param statements * @param schemaAttrs * @param entity */ function constructFilter(statements: Array, entity: string) { const { schemaAttrs, fulltextIndex } = Schema[entity]; const members: Array = [ // id: Q_StringValue factory.createPropertySignature( undefined, factory.createIdentifier('id'), undefined, factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier('Q_StringValue'), ), factory.createTypeReferenceNode( factory.createQualifiedName( factory.createIdentifier("SubQuery"), factory.createIdentifier(`${entity}IdSubQuery`) ) ) ]) ), // $$createAt$$: Q_DateValue factory.createPropertySignature( undefined, factory.createIdentifier('$$createAt$$'), undefined, factory.createTypeReferenceNode( factory.createIdentifier('Q_DateValue'), ) ), // $$seq$$: Q_StringValue factory.createPropertySignature( undefined, factory.createIdentifier('$$seq$$'), undefined, factory.createTypeReferenceNode( factory.createIdentifier('Q_StringValue'), ) ), // $$updateAt$$: Q_DateValue factory.createPropertySignature( undefined, factory.createIdentifier('$$updateAt$$'), undefined, factory.createTypeReferenceNode( factory.createIdentifier('Q_DateValue'), ) ) ]; const { [entity]: manyToOneSet } = ManyToOne; for (const attr of schemaAttrs) { const { type, name } = attr; const attrName = (name).text; if (ts.isTypeReferenceNode(type!)) { const { typeName } = type; if (ts.isIdentifier(typeName)) { const { text } = typeName; let type2: ts.TypeNode; switch (text) { case 'String': case 'Text': case 'Image': case 'File': { if (ReversePointerRelations[entity] && attrName === 'entity') { type2 = factory.createTypeReferenceNode('E'); } else { type2 = factory.createTypeReferenceNode( factory.createIdentifier('Q_StringValue'), ); } break; } case 'Int': case 'Uint': case 'Float': case 'Double': { type2 = factory.createTypeReferenceNode( factory.createIdentifier('Q_NumberValue'), ); break; } case 'Boolean': { type2 = factory.createTypeReferenceNode( factory.createIdentifier('Q_BooleanValue'), ); break; } case 'Datetime': { type2 = factory.createTypeReferenceNode( factory.createIdentifier('Q_DateValue'), ); break; } case 'SingleGeo': case 'Geo': { // geo类型暂时只支持通过expr查询 break; } case 'Object': { type2 = factory.createTypeReferenceNode( factory.createIdentifier('Object'), ); break; } default: { const text2 = text === 'Schema' ? entity : text; const manyToOneItem = manyToOneSet && manyToOneSet.find( ([refEntity]) => refEntity === text2 ); if (manyToOneItem) { // 外键可能落到相应的子查询中 members.push( factory.createPropertySignature( undefined, `${(name).text}Id`, undefined, factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier('Q_StringValue'), ), factory.createTypeReferenceNode( factory.createQualifiedName( factory.createIdentifier("SubQuery"), factory.createIdentifier(`${text2}IdSubQuery`) ), undefined ) ]) ) ); type2 = factory.createTypeReferenceNode( createForeignRef(entity, text2, 'Filter') ); } else { // 这里应该都是引用某个UnionType类型的定义了,如何判断? // const words = getStringTextFromUnionStringLiterals(); type2 = factory.createTypeReferenceNode( factory.createIdentifier('Q_EnumValue'), [ factory.createTypeReferenceNode( factory.createIdentifier(text), undefined ) ] ); } } } if (type2!) { members.push( factory.createPropertySignature( undefined, name, undefined, type2 ) ); } } } else if (ts.isUnionTypeNode(type!) && ts.isLiteralTypeNode(type.types[0]) || ts.isLiteralTypeNode(type!)) { members.push( factory.createPropertySignature( undefined, name, undefined, factory.createTypeReferenceNode( factory.createIdentifier('Q_EnumValue'), [ type ] ) ) ); } else { // 此时应当是引用本地定义的shape } } // type AttrFilter = {}; if (ReversePointerRelations[entity]) { // 有反向指针,将反向指针关联的对象的Filter也注入 ReversePointerRelations[entity].forEach( ele => members.push( factory.createPropertySignature( undefined, firstLetterLowerCase(ele), undefined, factory.createTypeReferenceNode( createForeignRef(entity, ele, 'Filter') ) ) ) ); } const eumUnionTypeNode: ts.TypeNode[] = ReversePointerRelations[entity] && ReversePointerRelations[entity].map( ele => factory.createLiteralTypeNode( factory.createStringLiteral(firstLetterLowerCase(ele)) ) ); if (process.env.COMPLING_AS_LIB) { eumUnionTypeNode && eumUnionTypeNode.push( factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ); } statements.push( factory.createTypeAliasDeclaration( undefined, undefined, factory.createIdentifier('AttrFilter'), ReversePointerRelations[entity] ? [ factory.createTypeParameterDeclaration( undefined, factory.createIdentifier("E"), undefined, ) ] : undefined, factory.createTypeLiteralNode(members) ) ); /** * export type Filter = AttrFilter | Partial | { [F in Q_LogicKey]: Filter[]; } | { [F in Q_FullTextKey]: Q_FullTextValue; }>; */ const types: ts.TypeNode[] = [ factory.createTypeReferenceNode( factory.createIdentifier("AttrFilter"), ReversePointerRelations[entity] ? [factory.createTypeReferenceNode('E')] : undefined ), factory.createTypeReferenceNode( factory.createIdentifier("ExprOp"), [ process.env.COMPLING_AS_LIB ? factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier('OpAttr') ), factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ]) : factory.createTypeReferenceNode( factory.createIdentifier('OpAttr') ) ] ), ]; // 如果还有其它类型的查询如全文,则加在types数组中 if (fulltextIndex) { types.push( factory.createTypeReferenceNode('FulltextFilter') ); } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Filter"), ReversePointerRelations[entity] ? [ factory.createTypeParameterDeclaration( undefined, factory.createIdentifier("E"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Q_EnumValue"), [ factory.createUnionTypeNode( eumUnionTypeNode ) ] ) ) ] : undefined, factory.createTypeReferenceNode( factory.createIdentifier("MakeFilter"), [factory.createIntersectionTypeNode(types)] ) ) ); } /** * 构造Projection和OneAttrProjection * @param statements * @param entity */ function constructProjection(statements: Array, entity: string) { const { schemaAttrs } = Schema[entity]; const properties: Array<[string | ts.PropertyName, boolean, ts.TypeNode?/* , ts.TypeNode? */]> = [ ['id', false], ['$$createAt$$', false], ['$$updateAt$$', false], ['$$seq$$', false], ]; const foreignKeyProperties: { [k: string]: [string] } = { [entity]: [''], }; const { [entity]: manyToOneSet } = ManyToOne; for (const attr of schemaAttrs) { const { type, name } = attr; const attrName = (name).text; if (ts.isTypeReferenceNode(type!)) { const { typeName } = type; if (ts.isIdentifier(typeName)) { const { text } = typeName; switch (text) { case 'String': case 'Text': case 'Int': case 'Float': case 'Double': case 'Boolean': case 'Datetime': case 'Image': case 'File': case 'SingleGeo': case 'Geo': case 'Object': { properties.push( [name, false] ) break; } default: { const text2 = text === 'Schema' ? entity : text; const manyToOneItem = manyToOneSet && manyToOneSet.find( ([refEntity]) => refEntity === text2 ); if (manyToOneItem) { // 外键投影 properties.push( [`${attrName}Id`, false, undefined], [name, false, factory.createTypeReferenceNode( createForeignRef(entity, text2, 'Projection') )/* , factory.createTypeReferenceNode( createForeignRef(entity, text2, 'ExportProjection') ) */] ); if (foreignKeyProperties.hasOwnProperty(text2)) { foreignKeyProperties[text2].push(attrName); } else { assign(foreignKeyProperties, { [text2]: [attrName], }); } } else { // todo 此处是对State的专门处理 if (text.endsWith('State')) { properties.push( [name, false, undefined] ); } else { // 引用的shape properties.push( [name, false, undefined] ); } } } } } else { assert(false); } } else { // 增加了本身object的shape定义 // assert(ts.isUnionTypeNode(type!) && ts.isLiteralTypeNode(type.types[0]) || ts.isLiteralTypeNode(type!)); properties.push( [name, false, undefined] ) } } if (ReversePointerRelations[entity]) { ReversePointerRelations[entity].forEach( (one) => { const text2 = one === 'Schema' ? entity : one; properties.push( [firstLetterLowerCase(one), false, factory.createTypeReferenceNode( createForeignRef(entity, one, 'Projection') )/* , factory.createTypeReferenceNode( createForeignRef(entity, one, 'ExportProjection') ) */] ); if (foreignKeyProperties.hasOwnProperty(one)) { foreignKeyProperties[text2].push('entity'); } else { assign(foreignKeyProperties, { [text2]: ['entity'], }); } } ) } // 一对多的projection const { [entity]: oneToManySet } = OneToMany; if (oneToManySet) { const foreignKeySet: Record = {}; for (const oneToManyItem of oneToManySet) { const [entityName, foreignKey] = oneToManyItem; if (foreignKeySet.hasOwnProperty(entityName)) { foreignKeySet[entityName].push(foreignKey); } else { foreignKeySet[entityName] = [foreignKey]; } } for (const entityName in foreignKeySet) { const entityNameLc = firstLetterLowerCase(entityName); foreignKeySet[entityName].forEach( (foreignKey) => { const identifier = `${entityNameLc}$${foreignKey}`; const aggrIdentifier = `${entityNameLc}$${foreignKey}$$aggr`; properties.push( [identifier, false, factory.createIntersectionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'Selection'), undefined ), factory.createTypeLiteralNode([ factory.createPropertySignature( undefined, factory.createIdentifier("$entity"), undefined, factory.createLiteralTypeNode(factory.createStringLiteral(firstLetterLowerCase(entityName))) ) ]) ])/* , factory.createIntersectionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'Exportation'), undefined ), factory.createTypeLiteralNode([ factory.createPropertySignature( undefined, factory.createIdentifier("$entity"), undefined, factory.createLiteralTypeNode(factory.createStringLiteral(firstLetterLowerCase(entityName))) ) ]) ]) */ ], [aggrIdentifier, false, factory.createIntersectionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'Aggregation'), undefined ), factory.createTypeLiteralNode([ factory.createPropertySignature( undefined, factory.createIdentifier("$entity"), undefined, factory.createLiteralTypeNode(factory.createStringLiteral(firstLetterLowerCase(entityName))) ) ]) ]) ] ); } ); } } const exprNode = factory.createTypeReferenceNode( factory.createIdentifier("Partial"), [ factory.createTypeReferenceNode( factory.createIdentifier("ExprOp"), [ process.env.COMPLING_AS_LIB ? factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier('OpAttr') ), factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ]) : factory.createTypeReferenceNode( factory.createIdentifier('OpAttr') ) ] ) ] ); const MetaPropertySignatures: ts.TypeElement[] = [ factory.createPropertySignature( undefined, factory.createStringLiteral("#id"), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( 'NodeId' ) ) ]; if (process.env.COMPLING_AS_LIB) { MetaPropertySignatures.push( factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("k"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) ) ) } // Projection,正常查询的投影 statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Projection"), undefined, factory.createIntersectionTypeNode([ factory.createTypeLiteralNode( MetaPropertySignatures.concat( properties.map( ([n, q, v]) => { return factory.createPropertySignature( undefined, n, q ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken), v || factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) ) } ) ) ), exprNode, ]) ) ); // ExportProjection,下载查询的投影 // 已经废弃。By Xc 2023.01.08 /* statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("ExportProjection"), undefined, factory.createIntersectionTypeNode([ factory.createTypeLiteralNode( MetaPropertySignaturs.concat( properties.map( ([n, q, v, v2]) => { return factory.createPropertySignature( undefined, n, factory.createToken(ts.SyntaxKind.QuestionToken), v2 || factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ) } ) ) ), exprNode, ]) ) ); */ // ${Entity}Projection,外键查询的专用投影 for (const foreignKey in foreignKeyProperties) { const identifier = `${foreignKey}IdProjection`; statements.push( factory.createTypeAliasDeclaration( undefined, undefined, factory.createIdentifier(identifier), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OneOf"), [ factory.createTypeLiteralNode( foreignKeyProperties[foreignKey].map( (attr) => factory.createPropertySignature( undefined, attr ? factory.createIdentifier(`${attr}Id`) : 'id', undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) ) ) ) ] ) ) ); } } /** * 构造Query * @param statements * @param entity */ function constructQuery(statements: Array, entity: string) { const entityLc = firstLetterLowerCase(entity); /* statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Query"), undefined, factory.createIntersectionTypeNode([ factory.createTypeLiteralNode([ // 这里可以不写entity了 factory.createPropertySignature( undefined, factory.createIdentifier("projection"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Projection"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("sort"), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ) ]), factory.createTypeReferenceNode( factory.createIdentifier("OakFilter"), [ factory.createLiteralTypeNode(factory.createStringLiteral("select")), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ) ] ), factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createTypeReferenceNode( factory.createIdentifier("Operation"), undefined ) ] ) ]) ) ); */ /** * export type ExportQuery = { entity: 'user'; projection: ExportProjection; filter?: Filter; sort?: Sorter; indexFrom?: number; count?: number; }; */ /* statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("ExportQuery"), undefined, factory.createIntersectionTypeNode([ factory.createTypeLiteralNode([ factory.createPropertySignature( undefined, factory.createIdentifier("projection"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("ExportProjection"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("sort"), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ) ]), factory.createTypeReferenceNode( factory.createIdentifier("OakFilter"), [ factory.createLiteralTypeNode(factory.createStringLiteral("select")), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ) ] ) ]) ) ); */ // 对每个可能的外键的子查询,建立相应的${Entity}IdSubQuery const { [entity]: manyToOneSet } = ManyToOne; let manyToSelf = false; if (manyToOneSet) { uniqBy(manyToOneSet, ([a]) => a).forEach( ([oneEntity, foreignKey]) => { statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(`${oneEntity}IdSubQuery`), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Selection"), [factory.createTypeReferenceNode( factory.createIdentifier(`${oneEntity}IdProjection`), undefined )] ) ) ); if (oneEntity === entity) { manyToSelf = true; } } ); } // 主键可能产生的子查询 if (!manyToSelf) { statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(`${entity}IdSubQuery`), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Selection"), [factory.createTypeReferenceNode( factory.createIdentifier(`${entity}IdProjection`), undefined )] ) ) ); } } /** * 构造Sort * @param statements * @param entity */ function constructSorter(statements: Array, entity: string) { const { schemaAttrs } = Schema[entity]; const members: Array = [ // id: 1 factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, factory.createIdentifier("id"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) )] ), // $$createAt$$: 1 factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, factory.createIdentifier("$$createAt$$"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) )] ), // $$seq$$: 1 factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, factory.createIdentifier("$$seq$$"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) )] ), // $$updateAt$$: 1 factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, factory.createIdentifier("$$updateAt$$"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) )] ), ]; const { [entity]: manyToOneSet } = ManyToOne; for (const attr of schemaAttrs) { const { type, name, questionToken } = attr; if (ts.isTypeReferenceNode(type!)) { const { typeName } = type; if (ts.isIdentifier(typeName)) { const { text } = typeName; let type2: ts.TypeNode; switch (text) { case 'String': case 'Text': case 'Int': case 'Float': case 'Double': case 'Boolean': case 'Datetime': case 'Image': case 'File': { type2 = factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); break; } default: { const text2 = text === 'Schema' ? entity : text; const manyToOneItem = manyToOneSet && manyToOneSet.find( ([refEntity]) => refEntity === text2 ); if (manyToOneItem) { type2 = factory.createTypeReferenceNode( createForeignRef(entity, text2, 'SortAttr') ); members.push( factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, factory.createIdentifier(`${(name).text}Id`), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) )] ) ); } else if (!['Object'].includes(text)) { // todo 对State的专门处理 type2 = factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); } } } if (type2!) { members.push( factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, name, undefined, type2 )] ) ); } } } else if (ts.isUnionTypeNode(type!) && ts.isLiteralTypeNode(type.types[0]) || ts.isLiteralTypeNode(type!)) { members.push( factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, name, undefined, factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword) )] ) ); } else { // 本地规定的shape,非结构化属性不参与排序 } } if (ReversePointerRelations[entity]) { ReversePointerRelations[entity].forEach( (one) => { members.push( factory.createTypeLiteralNode( [factory.createPropertySignature( undefined, firstLetterLowerCase(one), undefined, factory.createTypeReferenceNode( createForeignRef(entity, one, 'SortAttr') ) )] ) ); } ); } if (process.env.COMPLING_AS_LIB) { members.push( factory.createTypeLiteralNode([factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("k"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) )]) ); } members.push( factory.createTypeReferenceNode( factory.createIdentifier("OneOf"), [factory.createTypeReferenceNode( factory.createIdentifier("ExprOp"), [ process.env.COMPLING_AS_LIB ? factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier('OpAttr') ), factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ]) : factory.createTypeReferenceNode( factory.createIdentifier('OpAttr') ) ] )] ) ); /** * export type SortAttr = { id: 1; } | { $$createAt$$: 1; } | { $$updateAt$$: 1; } | { modiId: 1; } | { modi: Modi.SortAttr; } | { [k: string]: any; } | OneOf> */ statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("SortAttr"), undefined, factory.createUnionTypeNode(members) ) ); /** * export type SortNode = { $attr: SortAttr; $direction?: 'asc' | 'desc'; }; */ statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("SortNode"), undefined, factory.createTypeLiteralNode([ factory.createPropertySignature( undefined, factory.createIdentifier("$attr"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("SortAttr"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("$direction"), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ factory.createLiteralTypeNode(factory.createStringLiteral("asc")), factory.createLiteralTypeNode(factory.createStringLiteral("desc")) ]) ) ]) ) ); /** * export type Sorter = SortNode[]; */ statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Sorter"), undefined, factory.createArrayTypeNode(factory.createTypeReferenceNode( factory.createIdentifier("SortNode"), undefined )) ) ) } function constructFullAttrs(statements: Array, entity: string) { const { [entity]: manyToOneSet } = ManyToOne; const { [entity]: oneToManySet } = OneToMany; if (manyToOneSet && manyToOneSet.length) { const mtoAttrs: ts.TypeNode[] = []; for (const item of manyToOneSet) { const [one, key] = item; if (one === entity) { // 递归引用自身,因为typescript本身不支持递归,因此这里做一个显式的三层递归应该够用了 mtoAttrs.push( factory.createTemplateLiteralType( factory.createTemplateHead( `${key}.`, `${key}.` ), [factory.createTemplateLiteralTypeSpan( factory.createTypeReferenceNode( factory.createIdentifier("OpAttr"), undefined ), factory.createTemplateTail( "", "" ) )] ), factory.createTemplateLiteralType( factory.createTemplateHead( `${key}.${key}.`, `${key}.${key}.` ), [factory.createTemplateLiteralTypeSpan( factory.createTypeReferenceNode( factory.createIdentifier("OpAttr"), undefined ), factory.createTemplateTail( "", "" ) )] ), factory.createTemplateLiteralType( factory.createTemplateHead( `${key}.${key}.${key}.`, `${key}.${key}.${key}.` ), [factory.createTemplateLiteralTypeSpan( factory.createTypeReferenceNode( factory.createIdentifier("OpAttr"), undefined ), factory.createTemplateTail( "", "" ) )] ) ) } else { mtoAttrs.push( factory.createTemplateLiteralType( factory.createTemplateHead( `${key}.`, `${key}.` ), [factory.createTemplateLiteralTypeSpan( factory.createTypeReferenceNode( factory.createQualifiedName( factory.createIdentifier(one), factory.createIdentifier("NativeAttr") ), undefined ), factory.createTemplateTail( "", "" ) ) ] ) ); } } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("NativeAttr"), undefined, factory.createUnionTypeNode( [ factory.createTypeReferenceNode( factory.createIdentifier("OpAttr"), undefined ) , ...mtoAttrs ] ) ) ); } else { statements.push(factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("NativeAttr"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OpAttr"), undefined ) )); } const foreignKeySet: Record = {}; if (oneToManySet && oneToManySet.length > 0) { for (const oneToManyItem of oneToManySet) { const [entityName, foreignKey] = oneToManyItem; if (foreignKeySet.hasOwnProperty(entityName)) { foreignKeySet[entityName].push(foreignKey); } else { foreignKeySet[entityName] = [foreignKey]; } } const otmAttrs: ts.TemplateLiteralTypeNode[] = []; for (const entityName in foreignKeySet) { const entityNameLc = firstLetterLowerCase(entityName); if (foreignKeySet[entityName].length > 1) { foreignKeySet[entityName].forEach( (foreignKey) => { const head = `${entityNameLc}s$${foreignKey}`; otmAttrs.push( factory.createTemplateLiteralType( factory.createTemplateHead( `${head}$`, `${head}$` ), [ factory.createTemplateLiteralTypeSpan( factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), factory.createTemplateMiddle( ".", "." ) ), factory.createTemplateLiteralTypeSpan( factory.createTypeReferenceNode( entityName === entity ? factory.createIdentifier("NativeAttr") : factory.createQualifiedName( factory.createIdentifier(entityName), factory.createIdentifier("NativeAttr") ), undefined ), factory.createTemplateTail( "", "" ) ) ] ) ); } ); } else { otmAttrs.push( factory.createTemplateLiteralType( factory.createTemplateHead( `${entityNameLc}s$`, `${entityNameLc}s$` ), [ factory.createTemplateLiteralTypeSpan( factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), factory.createTemplateMiddle( ".", "." ) ), factory.createTemplateLiteralTypeSpan( factory.createTypeReferenceNode( entityName === entity ? factory.createIdentifier("NativeAttr") : factory.createQualifiedName( factory.createIdentifier(entityName), factory.createIdentifier("NativeAttr") ), undefined ), factory.createTemplateTail( "", "" ) ) ] ) ); } } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("FullAttr"), undefined, factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier("NativeAttr"), undefined ), ...otmAttrs ]) ) ); } else { statements.push(factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("FullAttr"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("NativeAttr"), undefined ) )); } } function constructActions(statements: Array, entity: string) { // Selection statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("SelectOperation"), [ factory.createTypeParameterDeclaration( undefined, factory.createIdentifier("P"), factory.createTypeReferenceNode( factory.createIdentifier("Object"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Projection"), undefined ) ) ], factory.createTypeReferenceNode( factory.createIdentifier("OakSelection"), [ factory.createLiteralTypeNode(factory.createStringLiteral("select")), factory.createTypeReferenceNode( factory.createIdentifier("P"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ] ) ), factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Selection"), [ factory.createTypeParameterDeclaration( undefined, factory.createIdentifier("P"), factory.createTypeReferenceNode( factory.createIdentifier("Object"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Projection"), undefined ) ) ], factory.createTypeReferenceNode( factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode( factory.createIdentifier("SelectOperation"), [ factory.createTypeReferenceNode( factory.createIdentifier("P"), undefined ) ] ), factory.createLiteralTypeNode(factory.createStringLiteral("action")) ] ) ), factory.createTypeAliasDeclaration( undefined, [factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Aggregation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("DeduceAggregation"), [ factory.createTypeReferenceNode( factory.createIdentifier("Projection"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ] ) ) ); // Exportation // 已经废弃,by Xc 2023.01.08 /* statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Exportation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("export")), factory.createTypeReferenceNode( factory.createIdentifier("ExportProjection"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ] ) ) ); */ const { [entity]: manyToOneSet } = ManyToOne; const { [entity]: oneToManySet } = OneToMany; const foreignKeySet: Record = {}; if (oneToManySet) { for (const oneToManyItem of oneToManySet) { const [entityName, foreignKey] = oneToManyItem; if (foreignKeySet.hasOwnProperty(entityName)) { foreignKeySet[entityName].push(foreignKey); } else { foreignKeySet[entityName] = [foreignKey]; } } } // CreateOperationData let foreignKeyAttr: string[] = []; if (ReversePointerEntities[entity]) { foreignKeyAttr.push( 'entity', 'entityId' ); } if (manyToOneSet) { for (const one of manyToOneSet) { if (!ReversePointerRelations[entity] || !ReversePointerRelations[entity].includes(one[1])) { foreignKeyAttr.push(`${one[1]}Id`); } } } let adNodes: ts.TypeNode[] = [ factory.createTypeReferenceNode( factory.createIdentifier("FormCreateData"), [ foreignKeyAttr.length > 0 ? factory.createTypeReferenceNode( factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ), factory.createUnionTypeNode(uniq(foreignKeyAttr).map( ele => factory.createLiteralTypeNode(factory.createStringLiteral(ele)) )) ] ) : factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ) ] ) ]; if (manyToOneSet) { /** * create的多对一有两种case * 如果关联对象是create,则对象的外键由关联对象的id决定 * 如果关联对象是update,则关联对象的filter由对象决定其主键 * 见cascadeStore */ const upsertOneNodes: ts.TypeNode[] = []; for (const one of manyToOneSet) { if (!ReversePointerRelations[entity] || !ReversePointerRelations[entity].includes(one[0])) { const oneEntity = one[0]; const cascadeCreateNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), one[2] ? factory.createToken(ts.SyntaxKind.QuestionToken) : undefined, factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'CreateSingleOperation') ) ) ] ); const cascadeUpdateNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), undefined, factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] ) ), factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'UpdateOperation') ) ) ] ); const noCascadeNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), one[2] ? factory.createToken(ts.SyntaxKind.QuestionToken) : undefined, factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] ) ) ] ); if (Schema[oneEntity].static) { upsertOneNodes.push(noCascadeNode); } else { switch (Schema[oneEntity].actionType) { case 'crud': case 'excludeRemove': { upsertOneNodes.push( factory.createUnionTypeNode([cascadeCreateNode, cascadeUpdateNode, noCascadeNode]) ); break; } case 'excludeUpdate': case 'appendOnly': { upsertOneNodes.push( factory.createUnionTypeNode([cascadeCreateNode, noCascadeNode]) ); break; } case 'readOnly': { upsertOneNodes.push(noCascadeNode); break; } default: { assert(false); } } } } } if (upsertOneNodes.length > 0) { adNodes.push( factory.createIntersectionTypeNode( upsertOneNodes ) ); } } const reverseOneNodes: ts.TypeNode[] = []; if (ReversePointerEntities[entity]) { if (ReversePointerRelations[entity]) { const { schemaAttrs } = Schema[entity]; const { questionToken } = schemaAttrs.find( ele => { const { name } = ele; return (name).text === 'entity' } )!; for (const one of ReversePointerRelations[entity]) { const cascadeCreateNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier('entity'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(one)), undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc factory.createTypeReferenceNode( createForeignRef(entity, one, 'CreateSingleOperation') ) ) ] ); const cascadeUpdateNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier('entity'), undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc factory.createLiteralTypeNode(factory.createStringLiteral(`${firstLetterLowerCase(one)}`) ) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] ) ), factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(one)), undefined, factory.createTypeReferenceNode( createForeignRef(entity, one, 'UpdateOperation') ) ) ] ); const noCascadeNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier('entity'), questionToken, factory.createLiteralTypeNode(factory.createStringLiteral(`${firstLetterLowerCase(one)}`) ) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), questionToken, factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] ) ) ] ); if (Schema[one].static) { reverseOneNodes.push(noCascadeNode); } else { switch (Schema[one].actionType) { case 'crud': case 'excludeRemove': { reverseOneNodes.push(cascadeCreateNode, cascadeUpdateNode, noCascadeNode); break; } case 'appendOnly': case 'excludeUpdate': { reverseOneNodes.push(cascadeCreateNode, noCascadeNode); break; } case 'readOnly': { reverseOneNodes.push(noCascadeNode); break; } default: { assert(false); } } } } } if (process.env.COMPLING_AS_LIB) { // 如果是base,要包容更多可能的反指 reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier('entity'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ), factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("K"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) ) ] ) ); } if (reverseOneNodes.length > 0) { adNodes.push( factory.createUnionTypeNode( reverseOneNodes ) ); } } // 一对多 const propertySignatures: ts.TypeElement[] = []; if (oneToManySet) { for (const entityName in foreignKeySet) { const entityNameLc = firstLetterLowerCase(entityName); foreignKeySet[entityName].forEach( (foreignKey) => { const identifier = `${entityNameLc}$${foreignKey}`; const otmCreateOperationDataNode = factory.createTypeReferenceNode( factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'CreateOperationData'), undefined ), factory.createUnionTypeNode(foreignKey === 'entity' ? [ factory.createLiteralTypeNode(factory.createStringLiteral("entity")), factory.createLiteralTypeNode(factory.createStringLiteral("entityId")) ] : [ factory.createLiteralTypeNode(factory.createStringLiteral(foreignKey)), factory.createLiteralTypeNode(factory.createStringLiteral(`${foreignKey}Id`)) ]) ] ); const otmCreateSingleOperationNode = factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("create")), otmCreateOperationDataNode ] ); const otmCreateMultipleOperationNode = factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("create")), factory.createArrayTypeNode( otmCreateOperationDataNode ) ] ); const otmUpdateOperationNode = factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createIndexedAccessTypeNode( factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'UpdateOperation'), undefined ), factory.createLiteralTypeNode(factory.createStringLiteral("action")) ), factory.createTypeReferenceNode( factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'UpdateOperationData'), undefined ), factory.createUnionTypeNode(foreignKey === 'entity' ? [ factory.createLiteralTypeNode(factory.createStringLiteral("entity")), factory.createLiteralTypeNode(factory.createStringLiteral("entityId")) ] : [ factory.createLiteralTypeNode(factory.createStringLiteral(foreignKey)), factory.createLiteralTypeNode(factory.createStringLiteral(`${foreignKey}Id`)) ]) ] ), factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'Filter'), undefined ) ] ); if (!Schema[entityName].static) { switch (Schema[entityName].actionType) { case 'crud': { propertySignatures.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ otmUpdateOperationNode, otmCreateMultipleOperationNode, factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createUnionTypeNode([ otmCreateSingleOperationNode, otmUpdateOperationNode ])] ) ]) ) ); break; } case 'appendOnly': case 'excludeUpdate': { propertySignatures.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ otmCreateMultipleOperationNode, factory.createTypeReferenceNode( factory.createIdentifier("Array"), [otmCreateSingleOperationNode] ) ]) ) ); break; } case 'readOnly': { break; } default: { assert(false); } } } } ); } } if (propertySignatures.length > 0) { adNodes.push( factory.createTypeLiteralNode( propertySignatures ) ); } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("CreateOperationData"), undefined, factory.createIntersectionTypeNode(adNodes) ) ); // CreateOperation statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("CreateSingleOperation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("create")), factory.createTypeReferenceNode( factory.createIdentifier("CreateOperationData") ) ] ) ), factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("CreateMultipleOperation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("create")), factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createTypeReferenceNode( factory.createIdentifier("CreateOperationData") )] ) ] ) ), factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("CreateOperation"), undefined, factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier("CreateSingleOperation") ), factory.createTypeReferenceNode( factory.createIdentifier("CreateMultipleOperation") ) ]) ) ); // UpdateOperationData foreignKeyAttr = []; if (ReversePointerRelations[entity]) { foreignKeyAttr.push('entity', 'entityId'); } if (manyToOneSet) { for (const one of manyToOneSet) { if (!ReversePointerRelations[entity] || !ReversePointerRelations[entity].includes(one[1])) { foreignKeyAttr.push(`${one[1]}Id`); } } } adNodes = [ factory.createTypeReferenceNode( factory.createIdentifier("FormUpdateData"), [ foreignKeyAttr.length > 0 ? factory.createTypeReferenceNode( factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ), factory.createUnionTypeNode(uniq(foreignKeyAttr).map( ele => factory.createLiteralTypeNode(factory.createStringLiteral(ele)) )) ] ) : factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ) ] ) ]; if (manyToOneSet) { /** * update的多对一有三种case * 如果关联对象是create,则对象的外键由关联对象的id决定 * 如果关联对象是update或者remove,则关联对象的filter由对象(的原行!注意这里的外键是不能变的!)决定其主键 * 见cascadeStore */ const upsertOneNodes: ts.TypeNode[] = []; for (const one of manyToOneSet) { if (!ReversePointerRelations[entity] || !ReversePointerRelations[entity].includes(one[0])) { const cascadeCreateNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), undefined, factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'CreateSingleOperation') ) ), factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), ] ); const cascadeUpdateNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), undefined, factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'UpdateOperation') ), ), factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), ] ); const cascadeRemoveNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), undefined, factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'RemoveOperation') ) ), factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), ] ); const noCascadeNode = factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), factory.createPropertySignature( undefined, factory.createIdentifier(`${one[1]}Id`), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode( [ factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] ), factory.createLiteralTypeNode(factory.createNull()) ] ) ), ] ); if (Schema[one[0]].static) { upsertOneNodes.push( noCascadeNode ); } else { switch (Schema[one[0]].actionType) { case 'crud': { upsertOneNodes.push( factory.createUnionTypeNode([cascadeCreateNode, cascadeUpdateNode, cascadeRemoveNode, noCascadeNode]) ); break; } case 'excludeUpdate': { upsertOneNodes.push( factory.createUnionTypeNode([cascadeCreateNode, cascadeRemoveNode, noCascadeNode]) ); break; } case 'appendOnly': { upsertOneNodes.push( factory.createUnionTypeNode([cascadeCreateNode, noCascadeNode]) ); break; } case 'readOnly': { upsertOneNodes.push( noCascadeNode ); break; } case 'excludeRemove': { upsertOneNodes.push( factory.createUnionTypeNode([cascadeCreateNode, cascadeUpdateNode, noCascadeNode]) ); break; } default: { assert(false); } } } } } if (upsertOneNodes.length > 0) { adNodes.push( factory.createIntersectionTypeNode( upsertOneNodes ) ); } const reverseOneNodes: ts.TypeNode[] = []; if (ReversePointerRelations[entity]) { const refEntityLitrals: (ts.TypeNode)[] = []; for (const one of ReversePointerRelations[entity]) { refEntityLitrals.push(factory.createLiteralTypeNode(factory.createStringLiteral(`${firstLetterLowerCase(one)}`))); const actionNodes: ts.TypeNode[] = []; if (!Schema[one].static) { switch (Schema[one].actionType) { case 'crud': { actionNodes.push( factory.createTypeReferenceNode( createForeignRef(entity, one, 'CreateSingleOperation') ), factory.createTypeReferenceNode( createForeignRef(entity, one, 'UpdateOperation') ), factory.createTypeReferenceNode( createForeignRef(entity, one, 'RemoveOperation') ) ); break; } case 'excludeUpdate': { actionNodes.push( factory.createTypeReferenceNode( createForeignRef(entity, one, 'CreateSingleOperation') ), factory.createTypeReferenceNode( createForeignRef(entity, one, 'RemoveOperation') ) ); break; } case 'excludeRemove': { actionNodes.push( factory.createTypeReferenceNode( createForeignRef(entity, one, 'CreateSingleOperation') ), factory.createTypeReferenceNode( createForeignRef(entity, one, 'UpdateOperation') ) ); break; } case 'appendOnly': { actionNodes.push( factory.createTypeReferenceNode( createForeignRef(entity, one, 'CreateSingleOperation') ) ); break; } case 'readOnly': { break; } default: { assert(false); } } } if (actionNodes.length > 0) { reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(one)), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode(actionNodes) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ), factory.createPropertySignature( undefined, factory.createIdentifier('entity'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword) ) ] ), ); } } if (process.env.COMPLING_AS_LIB) { // 如果是base,要包容更多可能的反指 refEntityLitrals.push( factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ); } reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier('entity'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode( [ factory.createUnionTypeNode(refEntityLitrals), factory.createLiteralTypeNode(factory.createNull()) ] ) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode( [ factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] ), factory.createLiteralTypeNode(factory.createNull()) ] ) ) ] ) ); } if (reverseOneNodes.length > 0) { adNodes.push( factory.createUnionTypeNode( reverseOneNodes ) ); } } const propertySignatures2: ts.TypeElement[] = []; if (process.env.COMPLING_AS_LIB) { propertySignatures2.push( factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("k"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) ) ); } if (oneToManySet) { for (const entityName in foreignKeySet) { const entityNameLc = firstLetterLowerCase(entityName); foreignKeySet[entityName].forEach( (foreignKey) => { const identifier = `${entityNameLc}$${foreignKey}`; const otmCreateOperationDataNode = factory.createTypeReferenceNode( factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'CreateOperationData'), undefined ), factory.createUnionTypeNode(foreignKey === 'entity' ? [ factory.createLiteralTypeNode(factory.createStringLiteral("entity")), factory.createLiteralTypeNode(factory.createStringLiteral("entityId")) ] : [ factory.createLiteralTypeNode(factory.createStringLiteral(foreignKey)), factory.createLiteralTypeNode(factory.createStringLiteral(`${foreignKey}Id`)) ]) ] ); const otmCreateSingleOperationNode = factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("create")), otmCreateOperationDataNode ] ) const otmCreateMultipleOperationNode = factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("create")), factory.createArrayTypeNode( otmCreateOperationDataNode ) ] ); const otmUpdateOperationNode = factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'UpdateOperation'), undefined ); const otmRemoveOperationNode = factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'RemoveOperation'), undefined ); if (!Schema[entityName].static) { switch (Schema[entityName].actionType) { case 'crud': { propertySignatures2.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ otmUpdateOperationNode, otmRemoveOperationNode, otmCreateMultipleOperationNode, factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createUnionTypeNode([ otmCreateSingleOperationNode, otmUpdateOperationNode, otmRemoveOperationNode ])] ) ]) ) ); break; } case 'excludeUpdate': { propertySignatures2.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ otmRemoveOperationNode, otmCreateMultipleOperationNode, factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createUnionTypeNode([ otmCreateSingleOperationNode, otmRemoveOperationNode ])] ) ]) ) ); break; } case 'excludeRemove': { propertySignatures2.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ otmUpdateOperationNode, otmCreateMultipleOperationNode, factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createUnionTypeNode([ otmCreateSingleOperationNode, otmUpdateOperationNode ])] ) ]) ) ); break; } case 'appendOnly': { propertySignatures2.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ otmCreateMultipleOperationNode, factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createUnionTypeNode([ otmCreateSingleOperationNode ])] ) ]) ) ); break; } case 'readOnly': { break; } default: { assert(false); } } } } ); } } if (propertySignatures2.length > 0) { adNodes.push( factory.createTypeLiteralNode( propertySignatures2 ) ); } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("UpdateOperationData"), undefined, factory.createIntersectionTypeNode(adNodes) ) ); // UpdateOperation const actionTypeNodes: ts.TypeNode[] = [factory.createLiteralTypeNode(factory.createStringLiteral("update"))]; if (ActionAsts[entity]) { actionTypeNodes.push( factory.createTypeReferenceNode('ParticularAction') ); } if (Schema[entity].hasRelationDef || entity === 'User') { actionTypeNodes.push( factory.createTypeReferenceNode('RelationAction') ); } if (process.env.COMPLING_AS_LIB) { actionTypeNodes.push( factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ); } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("UpdateOperation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createUnionTypeNode(actionTypeNodes), factory.createTypeReferenceNode( factory.createIdentifier("UpdateOperationData") ), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ] ) ) ); // RemoveOperationData adNodes = [ factory.createTypeLiteralNode([]) ]; if (manyToOneSet) { /** * remove的多对一有两种case * 如果关联对象动作是update或者remove,则关联对象的filter由对象(的原行!注意这里的外键是不能变的!)决定其主键 * 见cascadeStore */ const upsertOneNodes: ts.TypeNode[] = []; for (const one of manyToOneSet) { if (!ReversePointerRelations[entity] || !ReversePointerRelations[entity].includes(one[0])) { if (!Schema[one[0]].static) { switch (Schema[one[0]].actionType) { case 'crud': { upsertOneNodes.push( factory.createUnionTypeNode( [ factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'UpdateOperation') ), factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'RemoveOperation') ) ]) ) ] ) ] ) ); break; } case 'excludeUpdate': { upsertOneNodes.push( factory.createUnionTypeNode( [ factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'RemoveOperation') ) ) ] ) ] ) ); break; } case 'excludeRemove': { upsertOneNodes.push( factory.createUnionTypeNode( [ factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(one[1]), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( createForeignRef(entity, one[0], 'UpdateOperation') ) ) ] ) ] ) ); break; } case 'appendOnly': case 'readOnly': { break; } default: { assert(false); } } } } } const reverseOneNodes: ts.TypeNode[] = []; if (ReversePointerRelations[entity]) { const refEntityLitrals: (ts.TypeNode)[] = []; for (const one of ReversePointerRelations[entity]) { refEntityLitrals.push(factory.createLiteralTypeNode(factory.createStringLiteral(`${firstLetterLowerCase(one)}`))); if (!Schema[one].static) { switch (Schema[one].actionType) { case 'crud': { reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(one)), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, one, 'UpdateOperation') ), factory.createTypeReferenceNode( createForeignRef(entity, one, 'RemoveOperation') ) ]) ) ] ), ); break; } case 'excludeUpdate': { reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(one)), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( createForeignRef(entity, one, 'RemoveOperation') ) ) ] ), ); break; } case 'excludeRemove': { reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createPropertySignature( undefined, factory.createIdentifier(firstLetterLowerCase(one)), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( createForeignRef(entity, one, 'UpdateOperation') ) ) ] ), ); break; } case 'appendOnly': case 'readOnly': { break; } default: { assert(false); } } } } if (process.env.COMPLING_AS_LIB) { reverseOneNodes.push( factory.createTypeLiteralNode( [ factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("k"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) ) ] ) ) } } if (upsertOneNodes.length > 0) { adNodes.push( factory.createIntersectionTypeNode( upsertOneNodes ) ); } if (reverseOneNodes.length > 0) { adNodes.push( factory.createUnionTypeNode( reverseOneNodes ) ); } } /** * remove的同时进行cascade update或者cascade remove,感觉用触发器会更自然,因为在用户界面上似乎不会有对应的操作。 * 这部分代码暂时封闭 by Xc 20220501 **/ /* const propertySignatures3: ts.TypeElement[] = []; if (process.env.COMPLING_AS_LIB) { propertySignatures3.push( factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("k"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) ) ); } if (oneToManySet) { for (const entityName in foreignKeySet) { const entityNameLc = firstLetterLowerCase(entityName); foreignKeySet[entityName].forEach( (foreignKey) => { const identifier = `${entityNameLc}s$${foreignKey}`; propertySignatures3.push( factory.createPropertySignature( undefined, factory.createIdentifier(identifier), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'UpdateOperation'), undefined ), factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'RemoveOperation'), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Array"), [factory.createUnionTypeNode([ factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'UpdateOperation'), undefined ), factory.createTypeReferenceNode( createForeignRef(entity, entityName, 'RemoveOperation'), undefined ) ])] ) ]) ) ); } ); } } if (propertySignatures3.length > 0) { adNodes.push( factory.createTypeLiteralNode( propertySignatures3 ) ); } */ statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("RemoveOperationData"), undefined, factory.createIntersectionTypeNode(adNodes) ) ); // RemoveOperation statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("RemoveOperation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OakOperation"), [ factory.createLiteralTypeNode(factory.createStringLiteral("remove")), factory.createTypeReferenceNode( factory.createIdentifier("RemoveOperationData"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Filter"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("Sorter"), undefined ) ] ) ) ); statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Operation"), undefined, factory.createUnionTypeNode([ factory.createTypeReferenceNode( factory.createIdentifier("CreateOperation"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("UpdateOperation"), undefined ), factory.createTypeReferenceNode( factory.createIdentifier("RemoveOperation"), undefined ) ]) ) ); } const initialStatements = () => [ // import { String, Text, Int, SpecificKey } from 'oak-domain/types/DataType'; factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports( [ factory.createImportSpecifier( false, undefined, factory.createIdentifier('String') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Int') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Uint') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Float') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Double') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Boolean') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Text') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Datetime') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('File') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Image') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('PrimaryKey') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('ForeignKey') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Geo') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('SingleGeo') ) ] ) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN()}DataType`) ), /* import { Q_DateValue, Q_LogicKey, Q_NumberValue, FnCallKey, FnCallValue, Q_StringValue, Q_FullTextKey, Q_FullTextValue, FnCallValueAs, Q_BooleanValue, } from 'oak-domain/types/Demand'; */ factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports( [ factory.createImportSpecifier( false, undefined, factory.createIdentifier('Q_DateValue') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Q_BooleanValue') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Q_NumberValue') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Q_StringValue') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('Q_EnumValue') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('NodeId') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('MakeFilter') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('FulltextFilter') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('ExprOp') ), factory.createImportSpecifier( false, undefined, factory.createIdentifier('ExpressionKey') ), ] ) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN()}Demand`) ), factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([ factory.createImportSpecifier( false, undefined, factory.createIdentifier("OneOf") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ValueOf") ) ]) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN()}Polyfill`) ), // import * as SubQuery from '../_SubQuery'; factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamespaceImport(factory.createIdentifier("SubQuery")) ), factory.createStringLiteral("../_SubQuery") ), // import { Filter as OakFilter } from 'oak-domain/src/types/Entity'; factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([ factory.createImportSpecifier( false, undefined, factory.createIdentifier("FormCreateData") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("FormUpdateData") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("DeduceAggregation") ), factory.createImportSpecifier( false, factory.createIdentifier("Operation"), factory.createIdentifier("OakOperation") ), factory.createImportSpecifier( false, factory.createIdentifier("Selection"), factory.createIdentifier("OakSelection") ), factory.createImportSpecifier( false, factory.createIdentifier("MakeAction"), factory.createIdentifier("OakMakeAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("EntityShape") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("AggregationResult") ), ]) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN()}Entity`), undefined ) ]; function outputSubQuery(outputDir: string, printer: ts.Printer) { const statements: ts.Statement[] = []; if (process.env.COMPLING_AS_LIB) { } for (const entity in Schema) { // import * as User from '../User/Schema'; statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamespaceImport(factory.createIdentifier(entity)) ), factory.createStringLiteral(`./${entity}/Schema`) ) ); } const entities = keys(Schema); // 每个有manyToOne的Entity都会输出${One}IdSubQuery for (const one in Schema) { const identifier = `${one}IdSubQuery`; const fromEntites = OneToMany[one] ? uniq(OneToMany[one] .filter( ([e, f]) => f !== 'entity' ).map( ([e]) => e )) : []; fromEntites.push(one); const inUnionTypeNode: ts.TypeNode[] = fromEntites.map( ele => factory.createIntersectionTypeNode( [ factory.createTypeReferenceNode( factory.createQualifiedName( factory.createIdentifier(ele), factory.createIdentifier(identifier) ), undefined ), factory.createTypeLiteralNode([factory.createPropertySignature( undefined, factory.createIdentifier("entity"), undefined, factory.createLiteralTypeNode(factory.createStringLiteral(firstLetterLowerCase(ele))) )]) ] ) ); if (process.env.COMPLING_AS_LIB) { // 如果是建立 base,这里要加上额外可能的对象信息 inUnionTypeNode.push( factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) ); } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(identifier), undefined, factory.createMappedTypeNode( undefined, factory.createTypeParameterDeclaration( undefined, factory.createIdentifier("K"), factory.createUnionTypeNode([ factory.createLiteralTypeNode(factory.createStringLiteral("$in")), factory.createLiteralTypeNode(factory.createStringLiteral("$nin")) ]), undefined ), undefined, factory.createToken(ts.SyntaxKind.QuestionToken), factory.createUnionTypeNode( inUnionTypeNode ), undefined ) ) ); } const resultFile = ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS); const result = printer.printNode( ts.EmitHint.Unspecified, factory.createSourceFile(statements, factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None), resultFile ); const fileName = PathLib.join(outputDir, '_SubQuery.ts'); writeFileSync(fileName, result, { flag: 'w' }); } function outputEntityDict(outputDir: string, printer: ts.Printer) { const statements: ts.Statement[] = []; const propertySignatures: ts.PropertySignature[] = []; for (const entity in Schema) { // import * as User from '../User/Schema'; statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, factory.createIdentifier("EntityDef"), factory.createIdentifier(entity) )]) ), factory.createStringLiteral(`./${entity}/Schema`) ) ); const entityLc = firstLetterLowerCase(entity); propertySignatures.push( factory.createPropertySignature( undefined, factory.createIdentifier(entityLc), undefined, factory.createTypeReferenceNode(entity) ) ); } if (/* process.env.COMPLING_AS_LIB */false) { statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("EntityDef") )]) ), factory.createStringLiteral("../types/Entity"), undefined ), factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("EntityDict"), undefined, factory.createIntersectionTypeNode([ factory.createTypeLiteralNode( propertySignatures ), factory.createTypeLiteralNode([ factory.createIndexSignature( undefined, undefined, [factory.createParameterDeclaration( undefined, undefined, undefined, factory.createIdentifier("E"), undefined, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined )], factory.createTypeReferenceNode( factory.createIdentifier("EntityDef"), undefined ) ) ]) ]) ) ); } else { statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("EntityDict"), undefined, factory.createTypeLiteralNode( propertySignatures ) ) ); } const resultFile = ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS); const result = printer.printNode( ts.EmitHint.Unspecified, factory.createSourceFile(statements, factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None), resultFile ); const fileName = PathLib.join(outputDir, 'EntityDict.ts'); writeFileSync(fileName, result, { flag: 'w' }); } function outputSchema(outputDir: string, printer: ts.Printer) { for (const entity in Schema) { const statements: ts.Statement[] = initialStatements(); if (ActionAsts[entity]) { const { importedFrom, actionDefNames } = ActionAsts[entity]; const localActions: string[] = ['Action', 'ParticularAction']; for (const a in importedFrom) { assert(a.endsWith('Action')); const s = a.slice(0, a.length - 6).concat('State'); if (importedFrom[a] === 'local' && actionDefNames.includes(firstLetterLowerCase(a.slice(0, a.length - 6)))) { localActions.push(s); } else if (actionDefNames.includes(firstLetterLowerCase(a.slice(0, a.length - 6)))) { const { moduleSpecifier } = importedFrom[a] as ts.ImportDeclaration; statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports( [ factory.createImportSpecifier( false, undefined, factory.createIdentifier(s) ) ] ) ), moduleSpecifier, undefined ) ); } } statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports(localActions.map( ele => factory.createImportSpecifier( false, undefined, factory.createIdentifier(ele) ) )) ), factory.createStringLiteral('./Action'), undefined ), factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([ factory.createImportSpecifier( false, undefined, factory.createIdentifier("RelationAction") ), ]) ), factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), undefined ) ); } else { statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([ factory.createImportSpecifier( false, undefined, factory.createIdentifier("GenericAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("AppendOnlyAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ReadOnlyAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ExcludeUpdateAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("ExcludeRemoveAction") ), factory.createImportSpecifier( false, undefined, factory.createIdentifier("RelationAction") ), ]) ), factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), undefined ) ); } const { additionalImports } = Schema[entity]; if (additionalImports?.length > 0) { statements.push(...additionalImports); } // Relation定义加入 if (typeof Schema[entity].hasRelationDef === 'object' && ts.isTypeAliasDeclaration(Schema[entity].hasRelationDef as ts.Node)) { const node = Schema[entity].hasRelationDef as ts.TypeAliasDeclaration; statements.push( factory.updateTypeAliasDeclaration( node, undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], node.name, node.typeParameters, node.type ) ); } constructSchema(statements, entity); constructFilter(statements, entity); constructProjection(statements, entity); constructSorter(statements, entity); constructActions(statements, entity); constructQuery(statements, entity); // 现在FullAttrs和NativeAttrs似乎没什么用,还会引起递归 // constructFullAttrs(statements, entity); const makeActionArguments: ts.TypeNode[] = []; if (ActionAsts[entity]) { makeActionArguments.push(factory.createTypeReferenceNode('Action')); } else { makeActionArguments.push( factory.createTypeReferenceNode( OriginActionDict[Schema[entity].actionType as keyof typeof OriginActionDict], ) ); } if (Schema[entity].hasRelationDef || entity === 'User') { makeActionArguments.push( factory.createTypeReferenceNode('RelationAction') ); } const actionTypeNode: ts.TypeNode = factory.createTypeReferenceNode( factory.createIdentifier('OakMakeAction'), makeActionArguments.length === 1 ? makeActionArguments : [factory.createUnionTypeNode(makeActionArguments)] ); const EntityDefAttrs = [ factory.createPropertySignature( undefined, factory.createIdentifier("Schema"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Schema"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("OpSchema"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("Action"), undefined, process.env.COMPLING_AS_LIB ? factory.createUnionTypeNode( [ actionTypeNode, factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) ] ) : actionTypeNode ), factory.createPropertySignature( undefined, factory.createIdentifier("Selection"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Selection"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("Aggregation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Aggregation"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("Operation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("Operation"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("Create"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("CreateOperation"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("Update"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("UpdateOperation"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("Remove"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("RemoveOperation"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("CreateSingle"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("CreateSingleOperation"), undefined ) ), factory.createPropertySignature( undefined, factory.createIdentifier("CreateMulti"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("CreateMultipleOperation"), undefined ) ), ]; if (ActionAsts[entity]) { EntityDefAttrs.push( factory.createPropertySignature( undefined, factory.createIdentifier("ParticularAction"), undefined, factory.createTypeReferenceNode( factory.createIdentifier('ParticularAction'), undefined ) ) ); } if (typeof Schema[entity].hasRelationDef === 'object' && ts.isTypeAliasDeclaration(Schema[entity].hasRelationDef as ts.Node)) { EntityDefAttrs.push( factory.createPropertySignature( undefined, factory.createIdentifier("Relation"), undefined, factory.createTypeReferenceNode( factory.createIdentifier('Relation'), undefined ) ) ); } statements.push( factory.createTypeAliasDeclaration( undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("EntityDef"), undefined, factory.createTypeLiteralNode(EntityDefAttrs) ) ) const result = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements), Schema[entity].sourceFile); const fileName = PathLib.join(outputDir, entity, 'Schema.ts'); writeFileSync(fileName, result, { flag: 'w' }); } } function outputAction(outputDir: string, printer: ts.Printer) { const actionDictStatements: ts.Statement[] = []; const propertyAssignments: ts.PropertyAssignment[] = []; for (const entity in ActionAsts) { const { sourceFile, statements, importedFrom, actionDefNames } = ActionAsts[entity]; const importStatements: ts.Statement[] = []; for (const k in importedFrom) { assert(k.endsWith('Action')); if (importedFrom[k] !== 'local') { importStatements.push( importedFrom[k] as ts.ImportDeclaration ); } } /* const actionDiff = difference(actionNames, actionDefNames); if (actionDiff.length > 0) { throw new Error(`action not conform to actionDef: ${actionDiff.join(',')}, entity: ${entity}`); } */ statements.push( factory.createVariableStatement( [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList( [factory.createVariableDeclaration( factory.createIdentifier("ActionDefDict"), undefined, undefined, factory.createObjectLiteralExpression( actionDefNames.map( ele => factory.createPropertyAssignment( factory.createIdentifier(`${ele}State`), factory.createIdentifier(`${firstLetterUpperCase(ele)}ActionDef`) ) ), true ) )], ts.NodeFlags.Const ) ) ); /* const result = printer.printNode( ts.EmitHint.Unspecified, factory.createSourceFile(statements, factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None), sourceFile ); */ // 这里如果用printNode,stringLiteral的输出始终有个bug不知道如何处理 const result = printer.printList( ts.ListFormat.SourceFileStatements, factory.createNodeArray(importStatements.concat(statements)), sourceFile); const filename = PathLib.join(outputDir, entity, 'Action.ts'); writeFileSync(filename, result, { flag: 'w' }); actionDictStatements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, factory.createIdentifier("ActionDefDict"), factory.createIdentifier(entity) )]) ), factory.createStringLiteral(`./${entity}/Action`) ) ); propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier(firstLetterLowerCase(entity)), factory.createIdentifier(entity) ) ); } actionDictStatements.push( factory.createVariableStatement( [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList( [factory.createVariableDeclaration( factory.createIdentifier("ActionDefDict"), undefined, undefined, factory.createObjectLiteralExpression( propertyAssignments, true ) )], ts.NodeFlags.Const ) ) ); const resultFile = ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS); const result = printer.printNode( ts.EmitHint.Unspecified, factory.createSourceFile(actionDictStatements, factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None), resultFile ); const fileName = PathLib.join(outputDir, 'ActionDefDict.ts'); writeFileSync(fileName, result, { flag: 'w' }); } function constructAttributes(entity: string): ts.PropertyAssignment[] { const { schemaAttrs, enumStringAttrs } = Schema[entity]; const { [entity]: manyToOneSet } = ManyToOne; const result: ts.PropertyAssignment[] = []; schemaAttrs.forEach( (attr) => { const attrAssignments: ts.PropertyAssignment[] = []; const { name, type } = attr; let name2 = name; if (ts.isTypeReferenceNode(type!)) { const { typeName, typeArguments } = type; if (ts.isIdentifier(typeName)) { const { text } = typeName; switch (text) { case 'String': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("varchar") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [ factory.createPropertyAssignment( factory.createIdentifier("length"), factory.createNumericLiteral( ((typeArguments![0]).literal).text ) ), ], true ) ) ); // 如果是entity,在这里处理一下ref if (ts.isIdentifier(name) && name.text === 'entity') { const mtoRelations = ReversePointerRelations[entity]; if (mtoRelations) { const mtoEntities = mtoRelations.map( ele => firstLetterLowerCase(ele) ); attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("ref"), factory.createArrayLiteralExpression( mtoEntities.map( ele => factory.createStringLiteral(ele) ), false ) ) ); } } break; } case 'Text': case 'Image': case 'File': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("text") ), ); break; } case 'Int': case 'Uint': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("int") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [ factory.createPropertyAssignment( factory.createIdentifier("width"), factory.createNumericLiteral( ((typeArguments![0]).literal).text ) ), factory.createPropertyAssignment( factory.createIdentifier("signed"), text === 'Uint' ? factory.createFalse() : factory.createTrue() ) ], true ) ) ); break; } case 'Float': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("float") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [ factory.createPropertyAssignment( factory.createIdentifier("precision"), factory.createNumericLiteral( ((typeArguments![0]).literal).text ) ), factory.createPropertyAssignment( factory.createIdentifier("scale"), factory.createNumericLiteral( ((typeArguments![1]).literal).text ) ) ], true ) ) ); break; } case 'Double': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("double") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [ factory.createPropertyAssignment( factory.createIdentifier("precision"), factory.createNumericLiteral( ((typeArguments![0]).literal).text ) ), factory.createPropertyAssignment( factory.createIdentifier("scale"), factory.createNumericLiteral( ((typeArguments![1]).literal).text ) ) ], true ) ) ); break; } case 'Boolean': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("boolean") ) ); break; } case 'Datetime': { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("datetime") ), ); break; } case 'SingleGeo': case 'Geo': { // object类型暂不支持查询 attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("geometry") ), ); break; } case 'Object': { // object类型暂不支持查询 attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("object") ), ); break; } default: { const text2 = text === 'Schema' ? entity : text; const manyToOneItem = manyToOneSet && manyToOneSet.find( ([refEntity, attrName]) => refEntity === text2 && attrName === attrName ); if (manyToOneItem) { // 外键 name2 = factory.createIdentifier(`${(name).text}Id`); attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("ref") ), factory.createPropertyAssignment( factory.createIdentifier("ref"), factory.createStringLiteral(firstLetterLowerCase(text2)) ) ); } else { if (enumStringAttrs && enumStringAttrs.includes((name).text)) { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("varchar") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [factory.createPropertyAssignment( factory.createIdentifier("length"), factory.createNumericLiteral(STRING_LITERAL_MAX_LENGTH) )], true ) ) ); } else { // todo 引用的非string定义,目前没有处理int类型的引用,等遇到了再处理 attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("object") ), ); } } } } } else { assert(false); } } else { if (ts.isUnionTypeNode(type!)) { if (ts.isLiteralTypeNode(type.types[0])) { if (ts.isStringLiteral(type.types[0].literal)) { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("varchar") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [factory.createPropertyAssignment( factory.createIdentifier("length"), factory.createNumericLiteral(STRING_LITERAL_MAX_LENGTH) )], true ) ) ); } else { assert(ts.isNumericLiteral(type.types[0].literal)); attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("int") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [ factory.createPropertyAssignment( factory.createIdentifier("width"), factory.createNumericLiteral(INT_LITERL_DEFAULT_WIDTH) ) ], true ) ) ); } } else { // 否则是本地规定的shape,直接用object attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("object") ), ); } } else { if (ts.isLiteralTypeNode(type!)) { if (ts.isStringLiteral(type.literal)) { attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("varchar") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [factory.createPropertyAssignment( factory.createIdentifier("length"), factory.createNumericLiteral(STRING_LITERAL_MAX_LENGTH) )], true ) ) ); } else { assert(ts.isNumericLiteral(type.literal)); attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("precision") ), factory.createPropertyAssignment( factory.createIdentifier("params"), factory.createObjectLiteralExpression( [ factory.createPropertyAssignment( factory.createIdentifier("precision"), factory.createNumericLiteral(NUMERICAL_LITERL_DEFAULT_PRECISION) ), factory.createPropertyAssignment( factory.createIdentifier("scale"), factory.createNumericLiteral(NUMERICAL_LITERL_DEFAULT_SCALE) ) ], true ) ) ); } } else { // 否则是本地规定的shape,直接用object attrAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("type"), factory.createStringLiteral("object") ), ); } } } result.push( factory.createPropertyAssignment( name2, factory.createObjectLiteralExpression(attrAssignments, true) ) ); } ); return result; } function outputLocale(outputDir: string, printer: ts.Printer) { const locales: Record = {}; const entities: string[] = []; for (const entity in Schema) { const { locale, sourceFile } = Schema[entity]; if (locale) { const { properties } = locale; properties.forEach( (ele) => { assert(ts.isPropertyAssignment(ele) && (ts.isIdentifier(ele.name) || ts.isStringLiteral(ele.name)) && ts.isObjectLiteralExpression(ele.initializer)); const lng = ele.name.text; const result = printer.printList( ts.ListFormat.SourceFileStatements, factory.createNodeArray([ factory.createReturnStatement( ele.initializer ) ]), sourceFile); const data = Function(result)(); const filename = PathLib.join(outputDir, entity, 'locales', `${lng}.json`); writeFileSync(filename, JSON.stringify(data), { flag: 'w' }); if (locales[lng]) { locales[lng].push(entity); } else { locales[lng] = [entity]; } } ); entities.push(entity); } } for (const lng in locales) { if (locales[lng].length < entities.length) { const lack = difference(entities, locales[lng]); throw new Error(`${lng}语言定义中缺少了对象${lack.join(',')}的定义,请检查相应的定义文件`); } /* const statements: ts.Statement[] = locales[lng].map( (entity) => factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, factory.createIdentifier(firstLetterLowerCase(entity)), undefined ), factory.createStringLiteral(`../${entity}/locales/${lng}`), undefined ) ); statements.push( factory.createExportAssignment( undefined, undefined, undefined, factory.createObjectLiteralExpression( locales[lng].map( ele => factory.createShorthandPropertyAssignment( factory.createIdentifier(firstLetterLowerCase(ele)), undefined ) ), true ) ) ); const result = printer.printList( ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements), ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS)); const filename = path.join(outputDir, '_locales', `${lng}.ts`); writeFileSync(filename, result, { flag: 'w' }); */ } } function outputStorage(outputDir: string, printer: ts.Printer) { const importStatements: ts.Statement[] = [ factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("StorageSchema") )]) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN(1)}Storage`), undefined ), factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("EntityDict") )]) ), factory.createStringLiteral("./EntityDict"), undefined ) ]; const entityAssignments: ts.PropertyAssignment[] = []; for (const entity in Schema) { const indexExpressions: ts.Expression[] = []; const { sourceFile, inModi, indexes, toModi, actionType, static: _static, hasRelationDef } = Schema[entity]; const fromSchemaSpecifiers = [ factory.createImportSpecifier( false, undefined, factory.createIdentifier("OpSchema") ) ]; /* if (relationHierarchy || reverseCascadeRelationHierarchy) { fromSchemaSpecifiers.push( factory.createImportSpecifier( false, undefined, factory.createIdentifier("Relation") ) ); } */ const statements: ts.Statement[] = [ factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("StorageDesc") )]) ), factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN()}Storage`), undefined ), factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports(fromSchemaSpecifiers) ), factory.createStringLiteral("./Schema"), undefined ) ]; const needImportActions: ts.ImportSpecifier[] = []; switch (actionType) { case 'readOnly': { needImportActions.push( factory.createImportSpecifier( false, factory.createIdentifier("readOnlyActions"), factory.createIdentifier("actions") ) ); break; } case 'appendOnly': { needImportActions.push( factory.createImportSpecifier( false, factory.createIdentifier("appendOnlyActions"), factory.createIdentifier("actions") ) ); break; } case 'excludeUpdate': { needImportActions.push( factory.createImportSpecifier( false, factory.createIdentifier("excludeUpdateActions"), factory.createIdentifier("actions") ) ); break; } default: { if (ActionAsts[entity]) { statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([factory.createImportSpecifier( false, undefined, factory.createIdentifier("actions") )]) ), factory.createStringLiteral("./Action"), undefined ) ); } else { needImportActions.push( factory.createImportSpecifier( false, factory.createIdentifier("genericActions"), factory.createIdentifier("actions") ) ); } } } if (Schema[entity].hasRelationDef || entity === 'User') { needImportActions.push( factory.createImportSpecifier( false, undefined, factory.createIdentifier("relationActions"), ) ); } if (needImportActions.length > 0) { statements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports(needImportActions) ), factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), undefined ) ); } const propertyAssignments: (ts.PropertyAssignment | ts.ShorthandPropertyAssignment)[] = []; const attributes = constructAttributes(entity); propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("attributes"), factory.createObjectLiteralExpression( attributes, true ) ) ); if (indexes) { indexExpressions.push( ...indexes.elements ) } if (toModi) { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("toModi"), factory.createTrue() ) ); } if (inModi) { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("inModi"), factory.createTrue() ) ); } if (_static || actionType === 'readOnly') { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("static"), factory.createTrue() ) ); } propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("actionType"), factory.createStringLiteral(actionType) ) ); if (Schema[entity].hasRelationDef || entity === 'User') { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("actions"), factory.createCallExpression( factory.createPropertyAccessExpression( factory.createIdentifier("actions"), factory.createIdentifier("concat") ), undefined, [factory.createIdentifier("relationActions")] ) ) ); } else { propertyAssignments.push( factory.createShorthandPropertyAssignment( factory.createIdentifier("actions"), undefined ) ); } if (indexExpressions.length > 0) { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("indexes"), factory.createArrayLiteralExpression(indexExpressions, true) ) ); } /* if (relationHierarchy) { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("relationHierarchy"), relationHierarchy, ) ); } if (reverseCascadeRelationHierarchy) { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("reverseCascadeRelationHierarchy"), reverseCascadeRelationHierarchy, ) ); } */ if (hasRelationDef) { const { type } = hasRelationDef; if (ts.isUnionTypeNode(type)) { const { types } = type; const relationTexts = types.map( ele => { assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal)); return ele.literal.text; } ) propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("relation"), factory.createArrayLiteralExpression(relationTexts.map( ele => factory.createStringLiteral(ele) )), ) ); } else { assert(ts.isLiteralTypeNode(type)); assert(ts.isStringLiteral(type.literal)); propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("relation"), factory.createArrayLiteralExpression( [ type.literal ] ), ) ); } } const sdTypeArguments = [ factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ) ]; statements.push( factory.createVariableStatement( [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList( [factory.createVariableDeclaration( factory.createIdentifier("desc"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("StorageDesc"), sdTypeArguments ), factory.createObjectLiteralExpression( propertyAssignments, true ) )], ts.NodeFlags.Const ) ) ); const result = printer.printList( ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements), sourceFile); const filename = PathLib.join(outputDir, entity, 'Storage.ts'); writeFileSync(filename, result, { flag: 'w' }); importStatements.push( factory.createImportDeclaration( undefined, undefined, factory.createImportClause( false, undefined, factory.createNamedImports([ factory.createImportSpecifier( false, factory.createIdentifier("desc"), factory.createIdentifier(`${firstLetterLowerCase(entity)}Desc`) ) ]) ), factory.createStringLiteral(`./${entity}/Storage`), undefined ) ); entityAssignments.push( factory.createPropertyAssignment( firstLetterLowerCase(entity), factory.createIdentifier(`${firstLetterLowerCase(entity)}Desc`) ) ); } importStatements.push( factory.createVariableStatement( [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList( [factory.createVariableDeclaration( factory.createIdentifier("storageSchema"), undefined, factory.createTypeReferenceNode( factory.createIdentifier("StorageSchema"), [ factory.createTypeReferenceNode('EntityDict') ] ), factory.createObjectLiteralExpression( entityAssignments, true ) )], ts.NodeFlags.Const ) ) ); const result = printer.printList( ts.ListFormat.SourceFileStatements, factory.createNodeArray(importStatements), ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS)); const filename = PathLib.join(outputDir, 'Storage.ts'); writeFileSync(filename, result, { flag: 'w' }); } function resetOutputDir(outputDir: string) { emptydirSync(outputDir); for (const moduleName in Schema) { mkdirSync(PathLib.join(outputDir, moduleName)); mkdirSync(PathLib.join(outputDir, moduleName, 'locales')); } mkdirSync(PathLib.join(outputDir, '_locales')) } function addReverseRelationship() { for (const reverseEntity in ReversePointerRelations) { if (!ReversePointerEntities.hasOwnProperty(reverseEntity)) { throw new Error(`「${reverseEntity}」被引用为一个反指对象,但其定义中的entity和entityId不符合要求`); } for (const one of ReversePointerRelations[reverseEntity]) { addRelationship(reverseEntity, one, 'entity', false); } } } function outputIndexTs(outputDir: string) { const indexTs = `export * from './EntityDict'; export * from './Storage'; export * from './ActionDefDict'; `; const filename = PathLib.join(outputDir, 'index.ts'); writeFileSync(filename, indexTs, { flag: 'w' }); } function outputPackageJson(outputDir: string) { const pj = { "name": process.env.COMPLING_AS_LIB ? "general-app-domain" : "oak-app-domain", "main": "index.ts" }; const filename = PathLib.join(outputDir, 'package.json'); writeFileSync(filename, JSON.stringify(pj), { flag: 'w' }); } /** * (从toModi的对象开始)分析可能被modi指向的对象 */ function analyzeInModi() { const getRelateEntities = (entity: string) => { let result: string[] = []; if (ManyToOne[entity]) { // 用反指指针指向的对象可以忽略,因为前端不可能设计出这样的更新页面 result = ManyToOne[entity].filter( ele => ele[1] !== 'entity' ).map( ele => ele[0] ); } if (OneToMany[entity]) { result.push( ...OneToMany[entity].map( ele => ele[0] ) ); } return uniq(result); }; const setInModi = (entity: string) => { if (['Modi', 'ModiEntity', 'Oper', 'OperEntity', 'User'].includes(entity)) { return; } const schema = Schema[entity]; if (schema.toModi || schema.inModi || schema.actionType === 'readOnly' || schema.static) { return; } schema.inModi = true; const related = getRelateEntities(entity); related.forEach( ele => setInModi(ele) ); }; for (const entity in Schema) { if (Schema[entity].toModi) { const related = getRelateEntities(entity); related.forEach( ele => setInModi(ele) ); } } } export function analyzeEntities(inputDir: string, relativePath?: string) { const files = readdirSync(inputDir); const fullFilenames = files.map( ele => { const entity = ele.slice(0, ele.indexOf('.')) if (RESERVED_ENTITIES.includes(entity) || RESERVED_ENTITIES.find( ele2 => entity.startsWith(ele2) )) { throw new Error(`${ele}是系统保留字,请勿使用其当对象名或对象名前缀`); } return `${inputDir}/${ele}`; } ); const program = ts.createProgram(fullFilenames, { allowJs: true }); files.forEach( (filename) => { analyzeEntity(filename, inputDir, program, relativePath); } ); analyzeInModi(); uniqRelationships(); } export function buildSchema(outputDir: string): void { addReverseRelationship(); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); resetOutputDir(outputDir); outputSchema(outputDir, printer); outputLocale(outputDir, printer); outputSubQuery(outputDir, printer); outputAction(outputDir, printer); outputEntityDict(outputDir, printer); outputStorage(outputDir, printer); outputIndexTs(outputDir); if (!process.env.COMPLING_AS_LIB) { outputPackageJson(outputDir); } }