diff --git a/lib/base-app-domain/Modi/Storage.js b/lib/base-app-domain/Modi/Storage.js index 8b9200a..2058baf 100644 --- a/lib/base-app-domain/Modi/Storage.js +++ b/lib/base-app-domain/Modi/Storage.js @@ -38,10 +38,8 @@ exports.desc = { type: "object" }, iState: { - type: "varchar", - params: { - length: 24 - } + type: "enum", + enumeration: ["active", "applied", "abandoned"] } }, actionType: "crud", diff --git a/lib/base-app-domain/User/Storage.js b/lib/base-app-domain/User/Storage.js index ec86925..fb57def 100644 --- a/lib/base-app-domain/User/Storage.js +++ b/lib/base-app-domain/User/Storage.js @@ -25,10 +25,8 @@ exports.desc = { ref: "user" }, userState: { - type: "varchar", - params: { - length: 24 - } + type: "enum", + enumeration: ["normal", "merged"] } }, actionType: "crud", diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index e60d455..58c2ced 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -160,6 +160,13 @@ function checkActionDefNameConsistent(filename, actionDefNode) { var sName = stateNode.typeName.text.slice(0, stateNode.typeName.text.length - 5); (0, assert_1.default)(adfName === aName && aName === sName, "\u6587\u4EF6".concat(filename, "\u4E2D\u7684ActionDef").concat(name.text, "\u4E2DActionDef, Action\u548CState\u7684\u547D\u540D\u89C4\u5219\u4E0D\u4E00\u81F4")); } +function checkStringLiteralLegal(filename, obj, text, ele) { + (0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), "".concat(filename, "\u4E2D\u5F15\u7528\u7684").concat(obj, " ").concat(text, "\u4E2D\u5B58\u5728\u4E0D\u662Fstringliteral\u7684\u7C7B\u578B")); + (0, assert_1.default)(!ele.literal.text.includes('$'), "".concat(filename, "\u4E2D\u5F15\u7528\u7684action").concat(text, "\u4E2D\u7684").concat(obj, "\u300C").concat(ele.literal.text, "\u300D\u5305\u542B\u975E\u6CD5\u5B57\u7B26$")); + (0, assert_1.default)(ele.literal.text.length > 0, "".concat(filename, "\u4E2D\u5F15\u7528\u7684action").concat(text, "\u4E2D\u7684").concat(obj, "\u300C").concat(ele.literal.text, "\u300D\u957F\u5EA6\u975E\u6CD5")); + (0, assert_1.default)(ele.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "".concat(filename, "\u4E2D\u5F15\u7528\u7684").concat(obj, " ").concat(text, "\u4E2D\u7684\u300C").concat(ele.literal.text, "\u300D\u957F\u5EA6\u8FC7\u957F")); + return ele.literal.text; +} function addActionSource(moduleName, name, node) { var _a; var ast = ActionAsts[moduleName]; @@ -199,20 +206,13 @@ function getStringTextFromUnionStringLiterals(moduleName, filename, node, progra _a[name.text] = 'local', _a)); } - var getStringLiteral = function (ele) { - (0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), "".concat(filename, "\u4E2D\u5F15\u7528\u7684action").concat(name.text, "\u4E2D\u5B58\u5728\u4E0D\u662Fstringliteral\u7684\u7C7B\u578B")); - (0, assert_1.default)(!ele.literal.text.includes('$'), "".concat(filename, "\u4E2D\u5F15\u7528\u7684action").concat(name.text, "\u4E2D\u7684action\u300C").concat(ele.literal.text, "\u300D\u5305\u542B\u975E\u6CD5\u5B57\u7B26$")); - (0, assert_1.default)(ele.literal.text.length > 0, "".concat(filename, "\u4E2D\u5F15\u7528\u7684action").concat(name.text, "\u4E2D\u7684action\u300C").concat(ele.literal.text, "\u300D\u957F\u5EA6\u975E\u6CD5")); - (0, assert_1.default)(ele.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "".concat(filename, "\u4E2D\u5F15\u7528\u7684action").concat(name.text, "\u4E2D\u7684action\u300C").concat(ele.literal.text, "\u300D\u957F\u5EA6\u8FC7\u957F")); - return ele.literal.text; - }; if (ts.isUnionTypeNode(type)) { - var actions = type.types.map(function (ele) { return getStringLiteral(ele); }); + var actions = type.types.map(function (ele) { return checkStringLiteralLegal(filename, 'action', name.text, ele); }); return actions; } else { (0, assert_1.default)(ts.isLiteralTypeNode(type), "".concat(filename, "\u4E2D\u5F15\u7528\u7684action\u300C").concat(name.text, "\u300D\u7684\u5B9A\u4E49\u4E0D\u662Funion\u548CstringLiteral\u7C7B\u578B")); - var action = getStringLiteral(type); + var action = checkStringLiteralLegal(filename, 'action', name.text, type); return [action]; } } @@ -325,6 +325,25 @@ function checkLocaleExpressionPropertyExists(root, attr, exists, filename) { } }); } +function getStringEnumValues(filename, program, obj, node) { + var _a; + var checker = program.getTypeChecker(); + var symbol = checker.getSymbolAtLocation(node.typeName); + var declaration = symbol === null || symbol === void 0 ? void 0 : symbol.getDeclarations()[0]; + if (ts.isImportSpecifier(declaration)) { + var typee = checker.getDeclaredTypeOfSymbol(symbol); + declaration = (_a = typee.aliasSymbol) === null || _a === void 0 ? void 0 : _a.getDeclarations()[0]; + } + if (declaration && ts.isTypeAliasDeclaration(declaration)) { + if (ts.isUnionTypeNode(declaration.type) && ts.isLiteralTypeNode(declaration.type.types[0])) { + return declaration.type.types.map(function (ele) { return checkStringLiteralLegal(filename, obj, declaration.name.text, ele); }); + } + if (ts.isLiteralTypeNode(declaration.type)) { + var value = checkStringLiteralLegal(filename, obj, declaration.name.text, declaration.type); + return [value]; + } + } +} function analyzeEntity(filename, path, program, relativePath) { var _a; var fullPath = "".concat(path, "/").concat(filename); @@ -347,9 +366,7 @@ function analyzeEntity(filename, path, program, relativePath) { var toModi = false; var actionType = 'crud'; var _static = false; - var enumStringAttrs = []; - var states = []; - var localEnumStringTypes = []; + var enumAttributes = {}; var additionalImports = []; var localeDef = undefined; // let relationHierarchy: ts.ObjectLiteralExpression | undefined = undefined; @@ -419,8 +436,9 @@ function analyzeEntity(filename, path, program, relativePath) { } else { schemaAttrs.push(attrNode); - if (localEnumStringTypes.includes(type.typeName.text)) { - enumStringAttrs.push(name.text); + var enumStringValues = getStringEnumValues(filename, program, '属性', type); + if (enumStringValues) { + enumAttributes[attrName] = enumStringValues; } } } @@ -447,15 +465,19 @@ function analyzeEntity(filename, path, program, relativePath) { } else { schemaAttrs.push(attrNode); - if (ts.isUnionTypeNode(type)) { + if (ts.isUnionTypeNode(type) && ts.isLiteralTypeNode(type.types[0]) && ts.isStringLiteral(type.types[0].literal)) { + (0, assert_1.default)(ts.isIdentifier(name)); var types = type.types; - if (ts.isLiteralTypeNode(types[0]) && ts.isStringLiteral(types[0].literal)) { - enumStringAttrs.push(name.text); - types.forEach(function (ele) { - (0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), "\u300C".concat(filename, "\u300D\u4E0D\u652F\u6301\u6DF7\u5408\u578B\u7684\u679A\u4E3E\u5C5E\u6027\u5B9A\u4E49\u300C").concat(attrName, "\u300D")); - (0, assert_1.default)(ele.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "\u300C".concat(filename, "\u300D\u4E2D\u5B9A\u4E49\u7684\u5C5E\u6027\u679A\u4E3E\u300C").concat(attrName, "\u300D\u7684\u5B57\u7B26\u4E32\u957F\u5EA6\u5E94\u5C0F\u4E8E").concat(env_1.STRING_LITERAL_MAX_LENGTH)); - }); - } + var enumValues = types.map(function (ele) { return checkStringLiteralLegal(filename, '属性', name.text, ele); }); + enumAttributes[name.text] = enumValues; + } + else if (ts.isLiteralTypeNode(type) && ts.isStringLiteral(type.literal)) { + // 单个字符串的情形,目前应该没有,没测试过,先写着 by Xc 20230221 + (0, assert_1.default)(ts.isIdentifier(name)); + var enumValues = [ + checkStringLiteralLegal(filename, '属性', name.text, type) + ]; + enumAttributes[name.text] = enumValues; } } if (attrName === 'entity') { @@ -547,16 +569,19 @@ function analyzeEntity(filename, path, program, relativePath) { (0, assert_1.default)(!hasActionDef, "\u3010".concat(filename, "\u3011action\u5B9A\u4E49\u987B\u5728Relation\u4E4B\u540E")); (0, assert_1.default)(!localeDef, "\u3010".concat(filename, "\u3011locale\u5B9A\u4E49\u987B\u5728Relation\u4E4B\u540E")); // 增加userXXX对象的描述 + var relationValues = []; if (ts.isLiteralTypeNode(node.type)) { (0, assert_1.default)(ts.isStringLiteral(node.type.literal)); (0, assert_1.default)(node.type.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "Relation\u5B9A\u4E49\u7684\u5B57\u7B26\u4E32\u957F\u5EA6\u4E0D\u957F\u4E8E".concat(env_1.STRING_LITERAL_MAX_LENGTH, "\uFF08").concat(filename, "\uFF0C").concat(node.type.literal.text, "\uFF09")); + relationValues.push(node.type.literal.text); } else { (0, assert_1.default)(ts.isUnionTypeNode(node.type), "Relation\u7684\u5B9A\u4E49\u53EA\u80FD\u662Fstring\u7C7B\u578B\uFF08".concat(filename, "\uFF09")); - node.type.types.forEach(function (ele) { + relationValues.push.apply(relationValues, tslib_1.__spreadArray([], tslib_1.__read(node.type.types.map(function (ele) { (0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), "Relation\u7684\u5B9A\u4E49\u53EA\u80FD\u662Fstring\u7C7B\u578B\uFF08".concat(filename, "\uFF09")); (0, assert_1.default)(ele.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "Relation\u5B9A\u4E49\u7684\u5B57\u7B26\u4E32\u957F\u5EA6\u4E0D\u957F\u4E8E".concat(env_1.STRING_LITERAL_MAX_LENGTH, "\uFF08").concat(filename, "\uFF0C").concat(ele.literal.text, "\uFF09")); - }); + return ele.literal.text; + })), false)); } var entityLc = (0, string_1.firstLetterLowerCase)(moduleName); var relationEntityName = "User".concat(moduleName); @@ -569,7 +594,9 @@ function analyzeEntity(filename, path, program, relativePath) { _d[relationEntityName] = { schemaAttrs: relationSchemaAttrs, sourceFile: sourceFile, - enumStringAttrs: ['relation'], + enumAttributes: { + relation: relationValues, + }, actionType: 'excludeUpdate', additionalImports: [ factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("Relation"))])), factory.createStringLiteral("../".concat(moduleName, "/Schema")), undefined) @@ -611,58 +638,27 @@ function analyzeEntity(filename, path, program, relativePath) { 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(function (ele) { - (0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), "\u300C".concat(filename, "\u300D\u4E0D\u652F\u6301\u6DF7\u5408\u578B\u7684\u5E38\u91CF\u5B9A\u4E49\u300C").concat(node.name.text, "\u300D")); - (0, assert_1.default)(ele.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "\u300C".concat(filename, "\u300D\u4E2D\u5B9A\u4E49\u7684\u5E38\u91CF\u679A\u4E3E\u300C").concat(node.name.text, "\u300D\u7684\u5B57\u7B26\u4E32\u957F\u5EA6\u5E94\u5C0F\u4E8E").concat(env_1.STRING_LITERAL_MAX_LENGTH)); - }); - } } } if (ts.isVariableStatement(node)) { var declarations = node.declarationList.declarations; declarations.forEach(function (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); - var typeArguments = declaration.type.typeArguments; - (0, assert_1.default)(typeArguments.length === 2); - var _a = tslib_1.__read(typeArguments, 2), actionNode = _a[0], stateNode = _a[1]; - var checker = program.getTypeChecker(); - var symbol = checker.getSymbolAtLocation(actionNode.typeName); - var declaration2_1 = symbol.getDeclarations()[0]; - if (declaration2_1.getSourceFile() === sourceFile) { - // pushStatementIntoActionAst(moduleName, declaration2, sourceFile); - } - symbol = checker.getSymbolAtLocation(stateNode.typeName); - declaration2_1 = symbol.getDeclarations()[0]; - if (declaration2_1.getSourceFile() === sourceFile) { - // 检查state的定义合法 - (0, assert_1.default)(ts.isTypeAliasDeclaration(declaration2_1) && ts.isUnionTypeNode(declaration2_1.type), "\u300C".concat(filename, "\u300DState\u300C").concat(declaration2_1.name, "\u300D\u7684\u5B9A\u4E49\u53EA\u80FD\u662F\u6216\u7ED3\u70B9")); - declaration2_1.type.types.forEach(function (type) { - (0, assert_1.default)(ts.isLiteralTypeNode(type) && ts.isStringLiteral(type.literal), "\u300C".concat(filename, "\u300DState\u300C").concat(declaration2_1.name, "\u300D\u7684\u5B9A\u4E49\u53EA\u80FD\u662F\u5B57\u7B26\u4E32")); - (0, assert_1.default)(type.literal.text.length < env_1.STRING_LITERAL_MAX_LENGTH, "\u300C".concat(filename, "\u300DState\u300C").concat(type.literal.text, "\u300D\u7684\u957F\u5EA6\u5927\u4E8E\u300C").concat(env_1.STRING_LITERAL_MAX_LENGTH, "\u300D")); - }); - /* pushStatementIntoActionAst(moduleName, - factory.updateTypeAliasDeclaration( - declaration2, - declaration2.decorators, - [factory.createModifier(ts.SyntaxKind.ExportKeyword)], - declaration2.name, - declaration2.typeParameters, - declaration2.type - ), - sourceFile); */ - } - } + if (declaration.type && ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ActionDef') { + checkActionDefNameConsistent(filename, declaration); + var typeArguments = declaration.type.typeArguments; + (0, assert_1.default)(typeArguments.length === 2); + var _a = tslib_1.__read(typeArguments, 2), actionNode = _a[0], stateNode = _a[1]; + (0, assert_1.default)(ts.isTypeReferenceNode(actionNode)); + (0, assert_1.default)(ts.isTypeReferenceNode(stateNode)); + (0, assert_1.default)(getStringEnumValues(filename, program, 'action', actionNode), "\u6587\u4EF6".concat(filename, "\u4E2D\u7684action").concat(actionNode.typeName.text, "\u5B9A\u4E49\u4E0D\u662F\u5B57\u7B26\u4E32\u7C7B\u578B")); + var enumStateValues = getStringEnumValues(filename, program, 'state', stateNode); + (0, assert_1.default)(enumStateValues, "\u6587\u4EF6".concat(filename, "\u4E2D\u7684state").concat(stateNode.typeName.text, "\u5B9A\u4E49\u4E0D\u662F\u5B57\u7B26\u4E32\u7C7B\u578B")); pushStatementIntoActionAst(moduleName, node, sourceFile); + (0, assert_1.default)(ts.isIdentifier(declaration.name)); var adName = declaration.name.text.slice(0, declaration.name.text.length - 9); var attr = adName.concat('State'); - schemaAttrs.push(factory.createPropertySignature(undefined, factory.createIdentifier((0, string_1.firstLetterLowerCase)(attr)), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode(factory.createIdentifier(attr)))); - states.push((0, string_1.firstLetterLowerCase)(attr)); + schemaAttrs.push(factory.createPropertySignature(undefined, (0, string_1.firstLetterLowerCase)(attr), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode(attr))); + enumAttributes[(0, string_1.firstLetterLowerCase)(attr)] = enumStateValues; } else if (declaration.type && (ts.isArrayTypeNode(declaration.type) && ts.isTypeReferenceNode(declaration.type.elementType) @@ -783,13 +779,13 @@ function analyzeEntity(filename, path, program, relativePath) { }); indexes = declaration.initializer; } - else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { + else if (declaration.type && ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { // locale定义 var type = declaration.type, initializer = declaration.initializer; (0, assert_1.default)(ts.isObjectLiteralExpression(initializer)); var properties = initializer.properties; (0, assert_1.default)(properties.length > 0, "".concat(filename, "\u81F3\u5C11\u9700\u8981\u6709\u4E00\u79CDlocale\u5B9A\u4E49")); - var allEnumStringAttrs = enumStringAttrs.concat(states); + var allEnumStringAttrs = Object.keys(enumAttributes); var typeArguments = type.typeArguments; (0, assert_1.default)(typeArguments && ts.isTypeReferenceNode(typeArguments[0]) @@ -831,7 +827,7 @@ function analyzeEntity(filename, path, program, relativePath) { } localeDef = initializer; } - else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'Configuration') { + else if (declaration.type && ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'Configuration') { (0, assert_1.default)(!hasActionDef, "".concat(moduleName, "\u4E2D\u7684Configuration\u5B9A\u4E49\u5728Action\u4E4B\u540E")); (0, assert_1.default)(ts.isObjectLiteralExpression(declaration.initializer)); var properties = declaration.initializer.properties; @@ -878,7 +874,7 @@ function analyzeEntity(filename, path, program, relativePath) { actionType: actionType, static: _static, hasRelationDef: hasRelationDef, - enumStringAttrs: enumStringAttrs.concat(states), + enumAttributes: enumAttributes, additionalImports: additionalImports, }; if (hasFulltextIndex) { @@ -2898,7 +2894,7 @@ function outputAction(outputDir, printer) { (0, fs_1.writeFileSync)(fileName, result, { flag: 'w' }); } function constructAttributes(entity) { - var _a = Schema[entity], schemaAttrs = _a.schemaAttrs, enumStringAttrs = _a.enumStringAttrs; + var _a = Schema[entity], schemaAttrs = _a.schemaAttrs, enumAttributes = _a.enumAttributes; var _b = ManyToOne, _c = entity, manyToOneSet = _b[_c]; var result = []; schemaAttrs.forEach(function (attr) { @@ -2983,8 +2979,8 @@ function constructAttributes(entity) { attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("ref")), factory.createPropertyAssignment(factory.createIdentifier("ref"), factory.createStringLiteral((0, string_1.firstLetterLowerCase)(text2_6)))); } 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(env_1.STRING_LITERAL_MAX_LENGTH))], true))); + if (enumAttributes && enumAttributes[name.text]) { + attrAssignments.push(factory.createPropertyAssignment('type', factory.createStringLiteral("enum")), factory.createPropertyAssignment('enumeration', factory.createArrayLiteralExpression(enumAttributes[name.text].map(function (ele) { return factory.createStringLiteral(ele); })))); } else { // todo 引用的非string定义,目前没有处理int类型的引用,等遇到了再处理 @@ -3002,7 +2998,8 @@ function constructAttributes(entity) { 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(env_1.STRING_LITERAL_MAX_LENGTH))], true))); + (0, assert_1.default)(enumAttributes && enumAttributes[name.text]); + attrAssignments.push(factory.createPropertyAssignment('type', factory.createStringLiteral("enum")), factory.createPropertyAssignment('enumeration', factory.createArrayLiteralExpression(enumAttributes[name.text].map(function (ele) { return factory.createStringLiteral(ele); })))); } else { (0, assert_1.default)(ts.isNumericLiteral(type.types[0].literal)); diff --git a/lib/store/CascadeStore.js b/lib/store/CascadeStore.js index efdfc44..e3ecab9 100644 --- a/lib/store/CascadeStore.js +++ b/lib/store/CascadeStore.js @@ -583,28 +583,31 @@ var CascadeStore = /** @class */ (function (_super) { else { // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 - if (filter) { - if (filter.id && Object.keys(filter).length === 1) { - Object.assign(otm, { - filter: (0, filter_1.addFilterSegment)({ - entity: entity, - entityId: filter.id, - }, filterOtm), + // 暂时先封掉user上的相关更新条件,会制造出连接表上的update + if (entity !== 'user') { + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)({ + entity: entity, + entityId: filter.id, + }, filterOtm), + }); + } + else { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)((_a = {}, + _a[entity] = filter, + _a), filterOtm), + }); + } + } + if (action === 'remove' && actionOtm === 'update') { + Object.assign(dataOtm, { + entity: null, + entityId: null, }); } - else { - Object.assign(otm, { - filter: (0, filter_1.addFilterSegment)((_a = {}, - _a[entity] = filter, - _a), filterOtm), - }); - } - } - if (action === 'remove' && actionOtm === 'update') { - Object.assign(dataOtm, { - entity: null, - entityId: null, - }); } } } @@ -649,20 +652,23 @@ var CascadeStore = /** @class */ (function (_super) { // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 // 绝大多数情况都是id,但也有可能update可能出现上层filter不是根据id的(userEntityGrant的过期触发的wechatQrCode的过期,见general中的userEntityGrant的trigger) - if (filter) { - if (filter.id && Object.keys(filter).length === 1) { - Object.assign(otm, { - filter: (0, filter_1.addFilterSegment)((_d = {}, - _d[foreignKey_2] = filter.id, - _d), filterOtm), - }); - } - else { - Object.assign(otm, { - filter: (0, filter_1.addFilterSegment)((_e = {}, - _e[foreignKey_2.slice(0, foreignKey_2.length - 2)] = filter, - _e), filterOtm), - }); + // 暂时先封掉user上的连接,以避免生成连接表更新 + if (entity !== 'user') { + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)((_d = {}, + _d[foreignKey_2] = filter.id, + _d), filterOtm), + }); + } + else { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)((_e = {}, + _e[foreignKey_2.slice(0, foreignKey_2.length - 2)] = filter, + _e), filterOtm), + }); + } } } if (action === 'remove' && actionOtm === 'update') { diff --git a/lib/store/checker.d.ts b/lib/store/checker.d.ts index 0b1edae..f385b1b 100644 --- a/lib/store/checker.d.ts +++ b/lib/store/checker.d.ts @@ -2,8 +2,8 @@ import { AuthDefDict, Checker, EntityDict, OperateOption, SelectOption, StorageS import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { AsyncContext } from "./AsyncRowStore"; import { SyncContext } from './SyncRowStore'; -export declare function translateCheckerInAsyncContext>(checker: Checker): { - fn: Trigger['fn']; +export declare function translateCheckerInAsyncContext>(checker: Checker): { + fn: Trigger['fn']; when: 'before' | 'after'; }; export declare function translateCheckerInSyncContext>(checker: Checker): { diff --git a/lib/store/checker.js b/lib/store/checker.js index 38523ef..84a3be5 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -13,8 +13,8 @@ var relation_1 = require("./relation"); var uuid_1 = require("../utils/uuid"); function translateCheckerInAsyncContext(checker) { var _this = this; - var entity = checker.entity, type = checker.type, action = checker.action; - var when = ((action === 'create' || action instanceof Array && action.includes('create')) && ['relation'].includes(type)) ? 'after' : 'before'; + var entity = checker.entity, type = checker.type; + var when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 switch (type) { case 'data': { var checkerFn_1 = checker.checker; @@ -102,44 +102,36 @@ function translateCheckerInAsyncContext(checker) { }; } case 'relation': { - var relationFilter_1 = checker.relationFilter, errMsg_2 = checker.errMsg; + var relationFilter_1 = checker.relationFilter, errMsg = checker.errMsg; var fn = (function (_a, context, option) { var operation = _a.operation; return tslib_1.__awaiter(_this, void 0, void 0, function () { - var filter2, data, filter, _b, _c, _d; - return tslib_1.__generator(this, function (_e) { - switch (_e.label) { + var result, _b; + return tslib_1.__generator(this, function (_c) { + switch (_c.label) { case 0: if (context.isRoot()) { return [2 /*return*/, 0]; } - if (!(operation.action === 'create')) return [3 /*break*/, 3]; + if (!(typeof relationFilter_1 === 'function')) return [3 /*break*/, 2]; return [4 /*yield*/, relationFilter_1(operation, context, option)]; case 1: - filter2 = _e.sent(); - data = operation.data; - filter = data instanceof Array ? { - id: { - $in: data.map(function (ele) { return ele.id; }), - }, - } : { - id: data.id, - }; - return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter2, filter, true)]; + _b = _c.sent(); + return [3 /*break*/, 3]; case 2: - if (_e.sent()) { - return [2 /*return*/, 0]; - } - throw new Exception_1.OakUserUnpermittedException(errMsg_2); + _b = relationFilter_1; + _c.label = 3; case 3: - _b = operation; - _c = filter_1.combineFilters; - _d = [operation.filter]; - return [4 /*yield*/, relationFilter_1(operation, context, option)]; - case 4: - _b.filter = _c.apply(void 0, [_d.concat([_e.sent()])]); - _e.label = 5; - case 5: return [2 /*return*/, 0]; + result = _b; + if (result) { + if (operation.action === 'create') { + console.warn("".concat(entity, "\u5BF9\u8C61\u7684create\u7C7B\u578B\u7684checker\u4E2D\uFF0C\u5B58\u5728\u65E0\u6CD5\u8F6C\u6362\u4E3A\u8868\u8FBE\u5F0F\u5F62\u5F0F\u7684\u60C5\u51B5\uFF0C\u8BF7\u5C3D\u91CF\u4F7F\u7528authDef\u683C\u5F0F\u5B9A\u4E49\u8FD9\u7C7Bchecker")); + } + else { + operation.filter = (0, filter_1.combineFilters)([operation.filter, result]); + } + } + return [2 /*return*/, 0]; } }); }); @@ -181,8 +173,8 @@ function translateCheckerInAsyncContext(checker) { } exports.translateCheckerInAsyncContext = translateCheckerInAsyncContext; function translateCheckerInSyncContext(checker) { - var entity = checker.entity, type = checker.type, action = checker.action; - var when = ((action === 'create' || action instanceof Array && action.includes('create')) && ['relation'].includes(type)) ? 'after' : 'before'; + var entity = checker.entity, type = checker.type; + var when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 switch (type) { case 'data': { var checkerFn_3 = checker.checker; @@ -193,7 +185,7 @@ function translateCheckerInSyncContext(checker) { }; } case 'row': { - var filter_3 = checker.filter, errMsg_3 = checker.errMsg; + var filter_3 = checker.filter, errMsg_2 = checker.errMsg; var fn = function (operation, context, option) { var operationFilter = operation.filter, action = operation.action; var filter2 = typeof filter_3 === 'function' ? filter_3(operation, context, option) : filter_3; @@ -207,7 +199,7 @@ function translateCheckerInSyncContext(checker) { if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter, true)) { return; } - var e = new Exception_1.OakRowInconsistencyException(undefined, errMsg_3); + var e = new Exception_1.OakRowInconsistencyException(undefined, errMsg_2); throw e; } }; @@ -217,28 +209,25 @@ function translateCheckerInSyncContext(checker) { }; } case 'relation': { - var relationFilter_2 = checker.relationFilter, errMsg_4 = checker.errMsg; + var relationFilter_2 = checker.relationFilter, errMsg_3 = checker.errMsg; var fn = function (operation, context, option) { if (context.isRoot()) { return; } - var filter2 = typeof relationFilter_2 === 'function' ? relationFilter_2(operation, context, option) : relationFilter_2; - var filter = operation.filter, action = operation.action; - var filter3 = filter; - if (action === 'create') { - var data = operation.data; - filter3 = data instanceof Array ? { - id: { - $in: data.map(function (ele) { return ele.id; }), - }, - } : { id: data.id }; + var result = typeof relationFilter_2 === 'function' ? relationFilter_2(operation, context, option) : relationFilter_2; + (0, assert_1.default)(!(result instanceof Promise)); + if (result) { + var filter = operation.filter, action = operation.action; + if (action === 'create') { + console.warn("".concat(entity, "\u5BF9\u8C61\u7684create\u7C7B\u578B\u7684checker\u4E2D\uFF0C\u5B58\u5728\u65E0\u6CD5\u8F6C\u6362\u4E3A\u8868\u8FBE\u5F0F\u5F62\u5F0F\u7684\u60C5\u51B5\uFF0C\u8BF7\u5C3D\u91CF\u4F7F\u7528authDef\u683C\u5F0F\u5B9A\u4E49\u8FD9\u7C7Bchecker")); + return; + } + (0, assert_1.default)(filter); + if ((0, filter_1.checkFilterContains)(entity, context, result, filter, true)) { + return; + } + throw new Exception_1.OakUserUnpermittedException(errMsg_3); } - (0, assert_1.default)(filter3); - (0, assert_1.default)(!(filter2 instanceof Promise)); - if ((0, filter_1.checkFilterContains)(entity, context, filter2, filter3, true)) { - return; - } - throw new Exception_1.OakUserUnpermittedException(errMsg_4); }; return { fn: fn, @@ -265,9 +254,12 @@ function translateCheckerInSyncContext(checker) { } } exports.translateCheckerInSyncContext = translateCheckerInSyncContext; -function translateCascadeRelationFilterMaker(schema, lch, entity2) { +function translateCascadeRelationFilterMaker(schema, lch, entity2, pathPrefix) { var cascadePath = lch.cascadePath, relations = lch.relations; - var paths = cascadePath.split('.'); + var paths = cascadePath ? cascadePath.split('.') : []; + if (pathPrefix) { + paths.unshift(pathPrefix); + } var translateRelationFilter = function (entity) { // 有两种情况,此entity和user有Relation定义,或是此entity已经指向user if (entity === 'user') { @@ -312,11 +304,12 @@ function translateCascadeRelationFilterMaker(schema, lch, entity2) { }; var translateFilterMakerIter = function (entity, iter) { var relation = (0, relation_1.judgeRelation)(schema, entity, paths[iter]); + (0, assert_1.default)(relation === 2 || typeof relation === 'string'); if (iter === paths.length - 1) { if (relation === 2) { - var filterMaker_1 = translateRelationFilter(paths[iter]); + var filterMaker2_1 = translateRelationFilter(paths[iter]); return function (userId) { - var filter = filterMaker_1(userId); + var filter = filterMaker2_1(userId); (0, assert_1.default)(filter.id); return { entity: paths[iter], @@ -324,11 +317,10 @@ function translateCascadeRelationFilterMaker(schema, lch, entity2) { }; }; } - (0, assert_1.default)(typeof relation === 'string'); - var filterMaker_2 = translateRelationFilter(relation); + var filterMaker2_2 = translateRelationFilter(relation); return function (userId) { var _a; - var filter = filterMaker_2(userId); + var filter = filterMaker2_2(userId); (0, assert_1.default)(filter.id); return _a = {}, _a["".concat(paths[iter], "Id")] = filter.id, @@ -336,43 +328,272 @@ function translateCascadeRelationFilterMaker(schema, lch, entity2) { }; } else { - var subFilterMaker_1 = translateFilterMakerIter(paths[iter], iter + 1); - if (iter === 0) { - return function (userId) { - var _a; - var subFilter = subFilterMaker_1(userId); - return _a = {}, - _a[paths[iter]] = subFilter, - _a; - }; - } + var filterMaker_1 = relation === 2 ? translateFilterMakerIter(paths[iter], iter + 1) : translateFilterMakerIter(relation, iter + 1); return function (userId) { var _a; return (_a = {}, - _a[paths[iter]] = subFilterMaker_1(userId), + _a[paths[iter]] = filterMaker_1(userId), _a); }; } }; - var filter = cascadePath ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2); - return filter; -} -function translateActionAuthFilterMaker(schema, relationItem, entity) { - if (relationItem instanceof Array) { - var maker_1 = relationItem.map(function (ele) { - if (ele instanceof Array) { - return ele.map(function (ele2) { return translateCascadeRelationFilterMaker(schema, ele2, entity); }); - } - return [translateCascadeRelationFilterMaker(schema, ele, entity)]; - }); - return function (userId) { return ({ - $or: maker_1.map(function (ele) { return ({ - $and: ele.map(function (ele2) { return ele2(userId); }) - }); }) - }); }; + var filterMaker = paths.length ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2); + if (!paths.length) { + return function (oper, userId) { return filterMaker(userId); }; } - var filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity); - return function (userId) { return filterMaker(userId); }; + /** + * 针对第一层做一下特别优化,比如对象A指向对象B(多对一),如果A的cascadePath是 'B', + * 当create A时,会带有Bid。此时生成该B对象上的相关表达式查询返回,可以避免必须将此判定在对象创建之后再做 + * 另一使用场景是,在查询A时,如果带有Bid(在对象跳一对多子对象场景下很常见),可以提前判定这个查询对某些用户一定返回空集 + */ + var _a = tslib_1.__read(paths, 1), attr = _a[0]; + var relation = (0, relation_1.judgeRelation)(schema, entity2, attr); + (0, assert_1.default)(relation === 2 || typeof relation === 'string'); + var filterMaker2 = paths.length > 1 + ? (relation === 2 ? translateFilterMakerIter(attr, 1) : translateFilterMakerIter(relation, 1)) + : (relation === 2 ? translateRelationFilter(attr) : translateRelationFilter(relation)); + return function (operation, userId) { + var action = operation.action; + if (action === 'create') { + var data = operation.data; + var getForeignKeyId_1 = function (d) { + if (relation === 2) { + if (d.entity === attr && typeof d.entityId === 'string') { + return d.entitId; + } + throw new Exception_1.OakUserUnpermittedException(); + } + else { + (0, assert_1.default)(typeof relation === 'string'); + if (typeof d["".concat(attr, "Id")] === 'string') { + return d["".concat(attr, "Id")]; + } + throw new Exception_1.OakUserUnpermittedException(); + } + }; + if (relation === 2) { + if (data instanceof Array) { + var fkIds = (0, lodash_1.uniq)(data.map(function (d) { return getForeignKeyId_1(d); })); + return { + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: fkIds } }), + $count: fkIds.length, + }; + } + var fkId_1 = getForeignKeyId_1(data); + return { + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: fkId_1 }), + }; + } + (0, assert_1.default)(typeof relation === 'string'); + if (data instanceof Array) { + var fkIds = (0, lodash_1.uniq)(data.map(function (d) { return getForeignKeyId_1(d); })); + return { + $entity: relation, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: fkIds } }), + $count: fkIds.length, + }; + } + var fkId = getForeignKeyId_1(data); + return { + $entity: relation, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: fkId }), + }; + } + var filter = operation.filter; + if (relation === 2 && (filter === null || filter === void 0 ? void 0 : filter.entity) === attr && (filter === null || filter === void 0 ? void 0 : filter.entityId)) { + if (typeof filter.entityId === 'string') { + return { + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: filter.entityId }), + }; + } + else if (filter.entityId.$in && filter.entityId.$in instanceof Array) { + var entityIds = (0, lodash_1.uniq)(filter.entityId.$in); + return { + $entity: relation, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: entityIds } }), + $count: entityIds.length, + }; + } + } + else if (filter && filter["".concat(attr, "Id")]) { + if (typeof filter["".concat(attr, "Id")] === 'string') { + return { + $entity: attr, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: filter["".concat(attr, "Id")] }), + }; + } + else if (filter["".concat(attr, "Id")].$in && filter["".concat(attr, "Id")].$in instanceof Array) { + var entityIds = (0, lodash_1.uniq)(filter["".concat(attr, "Id")].$in); + return { + $entity: relation, + $filter: (0, filter_1.addFilterSegment)(filterMaker2(userId), { id: { $in: entityIds } }), + $count: entityIds.length, + }; + } + } + return filterMaker(userId); + }; +} +function translateActionAuthFilterMaker(schema, relationItem, entity, pathPrefix) { + if (relationItem instanceof Array) { + var maker = relationItem.map(function (ele) { + if (ele instanceof Array) { + return ele.map(function (ele2) { return translateCascadeRelationFilterMaker(schema, ele2, entity, pathPrefix); }); + } + return translateCascadeRelationFilterMaker(schema, ele, entity, pathPrefix); + }); + return maker; + } + var filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity, pathPrefix); + return filterMaker; +} +function makePotentialFilter(operation, context, filterMaker) { + var e_1, _a; + var userId = context.getCurrentUserId(); + (0, assert_1.default)(userId); + var filters = filterMaker instanceof Array ? filterMaker.map(function (ele) { + if (ele instanceof Array) { + return ele.map(function (ele2) { return ele2(operation, userId); }); + } + return ele(operation, userId); + }) : [filterMaker(operation, userId)]; + /** + * 在下面的逻辑中,如果某个maker返回的是$entity类型,则检查是否有满足条件的项,没有就要抛出异常,有就返回undefined + * undefined项即意味着该条件通过 + * 再加上and和or的布尔逻辑判断,得到最终结果 + * 还要考虑同步和异步…… + * 代码比较复杂,因为原先没有$entity这种返回结果的设计 + * by Xc 20130219 + */ + var filtersOr = []; + var isAsyncOr = false; + var _loop_1 = function (f) { + var e_2, _b; + if (f instanceof Array) { + var isAsyncAnd = true; + var filtersAnd = []; + var _loop_2 = function (ff) { + if (ff === null || ff === void 0 ? void 0 : ff.$entity) { + var _e = ff, $entity = _e.$entity, $filter = _e.$filter, _f = _e.$count, $count_1 = _f === void 0 ? 1 : _f; + var count = context.count($entity, { + filter: $filter, + }, {}); + if (count instanceof Promise) { + isAsyncAnd = true; + filtersAnd.push(count.then(function (c2) { + if (c2 >= $count_1) { + return undefined; + } + return new Exception_1.OakUserUnpermittedException(); + })); + } + else { + filtersAnd.push(count >= $count_1 ? undefined : new Exception_1.OakUserUnpermittedException()); + } + } + else if (ff) { + filtersAnd.push(ff); + } + }; + try { + for (var f_1 = (e_2 = void 0, tslib_1.__values(f)), f_1_1 = f_1.next(); !f_1_1.done; f_1_1 = f_1.next()) { + var ff = f_1_1.value; + _loop_2(ff); + } + } + catch (e_2_1) { e_2 = { error: e_2_1 }; } + finally { + try { + if (f_1_1 && !f_1_1.done && (_b = f_1.return)) _b.call(f_1); + } + finally { if (e_2) throw e_2.error; } + } + if (isAsyncAnd = true) { + isAsyncOr = true; + filtersOr.push(isAsyncAnd ? Promise.all(filtersAnd).then(function (fa) { + var e_3, _a; + var faR = []; + try { + for (var fa_1 = (e_3 = void 0, tslib_1.__values(fa)), fa_1_1 = fa_1.next(); !fa_1_1.done; fa_1_1 = fa_1.next()) { + var faItem = fa_1_1.value; + if (faItem instanceof Exception_1.OakUserUnpermittedException) { + return faItem; + } + else if (faItem) { + faR.push(faItem); + } + } + } + catch (e_3_1) { e_3 = { error: e_3_1 }; } + finally { + try { + if (fa_1_1 && !fa_1_1.done && (_a = fa_1.return)) _a.call(fa_1); + } + finally { if (e_3) throw e_3.error; } + } + if (faR.length > 0) { + return { + $and: faR, + }; + } + }) : { + $and: filtersAnd, + }); + } + } + else { + if (f === null || f === void 0 ? void 0 : f.$entity) { + var _c = f, $entity = _c.$entity, $filter = _c.$filter, _d = _c.$count, $count_2 = _d === void 0 ? 1 : _d; + var count = context.count($entity, { + filter: $filter, + }, {}); + if (count instanceof Promise) { + isAsyncOr = true; + filtersOr.push(count.then(function (c2) { return c2 >= $count_2 ? undefined : new Exception_1.OakUserUnpermittedException(); })); + } + else { + filtersOr.push(count >= $count_2 ? undefined : new Exception_1.OakUserUnpermittedException()); + } + } + else if (f) { + filtersOr.push(f); + } + } + }; + try { + for (var filters_1 = tslib_1.__values(filters), filters_1_1 = filters_1.next(); !filters_1_1.done; filters_1_1 = filters_1.next()) { + var f = filters_1_1.value; + _loop_1(f); + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (filters_1_1 && !filters_1_1.done && (_a = filters_1.return)) _a.call(filters_1); + } + finally { if (e_1) throw e_1.error; } + } + // or的逻辑是,有一个成功就直接通过 + var returnOrFilters = function (filters) { + if (filters.length === 0 || filters.includes(undefined)) { + return undefined; + } + var foFilters = filters.filter(function (ele) { return ele !== undefined && !(ele instanceof Exception_1.OakUserUnpermittedException); }); + if (foFilters.length > 0) { + return { + $or: foFilters, + }; + } + throw new Exception_1.OakUserUnpermittedException(); + }; + if (isAsyncOr) { + return Promise.all(filtersOr) + .then(function (filters) { return returnOrFilters(filters); }); + } + return returnOrFilters(filtersOr); } /** * 根据权限定义,创建出相应的checker @@ -382,75 +603,90 @@ function translateActionAuthFilterMaker(schema, relationItem, entity) { */ function createAuthCheckers(schema, authDict) { var checkers = []; - var _loop_1 = function (entity) { + var _loop_3 = function (entity) { var _a; if (authDict[entity]) { var _b = authDict[entity], relationAuth = _b.relationAuth, actionAuth = _b.actionAuth; if (relationAuth) { var raFilterMakerDict_1 = {}; - var userEntityName_1 = "user".concat((0, string_1.firstLetterUpperCase)(entity)); + var userEntityName = "user".concat((0, string_1.firstLetterUpperCase)(entity)); for (var r in relationAuth) { Object.assign(raFilterMakerDict_1, (_a = {}, - _a[r] = translateActionAuthFilterMaker(schema, relationAuth[r], entity), + _a[r] = translateActionAuthFilterMaker(schema, relationAuth[r], userEntityName, entity), _a)); } var entityIdAttr_1 = "".concat(entity, "Id"); checkers.push({ - entity: userEntityName_1, + entity: userEntityName, action: 'create', type: 'relation', relationFilter: function (operation, context) { - var _a; var data = operation.data; (0, assert_1.default)(!(data instanceof Array)); - var _b = data, relation = _b.relation, _c = entityIdAttr_1, entityId = _b[_c]; - var userId = context.getCurrentUserId(); + var _a = data, relation = _a.relation, _b = entityIdAttr_1, entityId = _a[_b]; if (!raFilterMakerDict_1[relation]) { return; } - var filter = raFilterMakerDict_1[relation](userId); - return _a = {}, - _a[entity] = filter, - _a; + var filter = makePotentialFilter(operation, context, raFilterMakerDict_1[relation]); + return filter; }, errMsg: '越权操作', }); checkers.push({ - entity: userEntityName_1, + entity: userEntityName, action: 'remove', type: 'relation', relationFilter: function (operation, context) { - var _a; - var userId = context.getCurrentUserId(); - var filter = operation.filter; - var makeFilterFromRows = function (rows) { - var relations = (0, lodash_1.uniq)(rows.map(function (ele) { return ele.relation; })); - var entityIds = (0, lodash_1.uniq)(rows.map(function (ele) { return ele[entityIdAttr_1]; })); - (0, assert_1.default)(entityIds.length === 1, "\u5728\u56DE\u6536".concat(userEntityName_1, "\u4E0A\u6743\u9650\u65F6\uFF0C\u5355\u6B21\u56DE\u6536\u6D89\u53CA\u5230\u4E86\u4E0D\u540C\u7684\u5BF9\u8C61\uFF0C\u6B64\u64CD\u4F5C\u4E0D\u88AB\u5141\u8BB8")); + // 目前过不去 + return undefined; + /* const userId = context.getCurrentUserId(); + const { filter } = operation as ED[keyof ED]['Remove']; + const makeFilterFromRows = (rows: Partial[]): SyncOrAsync => { + const relations = uniq(rows.map(ele => ele.relation)); + const entityIds = uniq(rows.map(ele => ele[entityIdAttr])); + assert(entityIds.length === 1, `在回收${userEntityName}上权限时,单次回收涉及到了不同的对象,此操作不被允许`); // const entityId = entityIds[0]!; + // 所有的relation条件要同时满足and关系(注意这里的filter翻译出来是在entity对象上,不是在userEntity对象上) - return { - $and: relations.map(function (relation) { return raFilterMakerDict_1[relation]; }).filter(function (ele) { return !!ele; }).map(function (ele) { - var _a; - return (_a = {}, - _a[entity] = ele(userId), - _a); - }) - }; + const filtersAnd = relations.map( + (relation) => raFilterMakerDict[relation!] + ).filter( + ele => !!ele + ).map( + ele => makePotentialFilter(operation, context, ele) + ); + if (filtersAnd.find(ele => ele instanceof Promise)) { + return Promise.all(filtersAnd).then( + (fa) => { + if (fa.length > 0) { + return { + $and: fa, + } as ED[keyof ED]['Selection']['filter']; + } + } + ); + } + if (filtersAnd.length > 0) { + return { + $and: filtersAnd + } as ED[keyof ED]['Selection']['filter']; + } }; - var toBeRemoved = context.select(userEntityName_1, { - data: (_a = { - id: 1, - relation: 1 - }, - _a[entityIdAttr_1] = 1, - _a), - filter: filter, + + const toBeRemoved = context.select(userEntityName, { + data: { + id: 1, + relation: 1, + [entityIdAttr]: 1, + }, + filter, }, { dontCollect: true }); if (toBeRemoved instanceof Promise) { - return toBeRemoved.then(function (rows) { return makeFilterFromRows(rows); }); + return toBeRemoved.then( + (rows) => makeFilterFromRows(rows) + ); } - return makeFilterFromRows(toBeRemoved); + return makeFilterFromRows(toBeRemoved); */ }, errMsg: '越权操作', }); @@ -458,7 +694,7 @@ function createAuthCheckers(schema, authDict) { // todo 等实现的时候再写 } if (actionAuth) { - var _loop_2 = function (a) { + var _loop_4 = function (a) { var filterMaker = translateActionAuthFilterMaker(schema, actionAuth[a], entity); checkers.push({ entity: entity, @@ -466,20 +702,20 @@ function createAuthCheckers(schema, authDict) { type: 'relation', relationFilter: function (operation, context) { // const { filter } = operation; - var filter = filterMaker(context.getCurrentUserId()); + var filter = makePotentialFilter(operation, context, filterMaker); return filter; }, errMsg: '定义的actionAuth中检查出来越权操作', }); }; for (var a in actionAuth) { - _loop_2(a); + _loop_4(a); } } } }; for (var entity in schema) { - _loop_1(entity); + _loop_3(entity); } return checkers; } @@ -491,7 +727,7 @@ exports.createAuthCheckers = createAuthCheckers; * 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功 */ function createRemoveCheckers(schema, authDict) { - var e_1, _a; + var e_4, _a; var checkers = []; // 先建立所有的一对多的关系 var OneToManyMatrix = {}; @@ -506,7 +742,7 @@ function createRemoveCheckers(schema, authDict) { } }; var addToMtoEntity = function (e, fs) { - var e_2, _a; + var e_5, _a; var _b; try { for (var fs_1 = tslib_1.__values(fs), fs_1_1 = fs_1.next(); !fs_1_1.done; fs_1_1 = fs_1.next()) { @@ -519,12 +755,12 @@ function createRemoveCheckers(schema, authDict) { } } } - catch (e_2_1) { e_2 = { error: e_2_1 }; } + catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (fs_1_1 && !fs_1_1.done && (_a = fs_1.return)) _a.call(fs_1); } - finally { if (e_2) throw e_2.error; } + finally { if (e_5) throw e_5.error; } } }; for (var entity in schema) { @@ -548,16 +784,16 @@ function createRemoveCheckers(schema, authDict) { } // 当删除一时,要确认多上面没有指向一的数据 var entities = (0, lodash_1.union)(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix)); - var _loop_3 = function (entity) { + var _loop_5 = function (entity) { checkers.push({ entity: entity, action: 'remove', type: 'logical', checker: function (operation, context, option) { - var e_3, _a, e_4, _b; + var e_6, _a, e_7, _b; var promises = []; if (OneToManyMatrix[entity]) { - var _loop_5 = function (otm) { + var _loop_7 = function (otm) { var _g, _h; var _j = tslib_1.__read(otm, 2), e = _j[0], attr = _j[1]; var proj = (_g = { @@ -594,21 +830,21 @@ function createRemoveCheckers(schema, authDict) { } }; try { - for (var _c = (e_3 = void 0, tslib_1.__values(OneToManyMatrix[entity])), _d = _c.next(); !_d.done; _d = _c.next()) { + for (var _c = (e_6 = void 0, tslib_1.__values(OneToManyMatrix[entity])), _d = _c.next(); !_d.done; _d = _c.next()) { var otm = _d.value; - _loop_5(otm); + _loop_7(otm); } } - catch (e_3_1) { e_3 = { error: e_3_1 }; } + catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } - finally { if (e_3) throw e_3.error; } + finally { if (e_6) throw e_6.error; } } } if (OneToManyOnEntityMatrix[entity]) { - var _loop_6 = function (otm) { + var _loop_8 = function (otm) { var _l, _m, _o; var proj = { id: 1, @@ -652,17 +888,17 @@ function createRemoveCheckers(schema, authDict) { } }; try { - for (var _e = (e_4 = void 0, tslib_1.__values(OneToManyOnEntityMatrix[entity])), _f = _e.next(); !_f.done; _f = _e.next()) { + for (var _e = (e_7 = void 0, tslib_1.__values(OneToManyOnEntityMatrix[entity])), _f = _e.next(); !_f.done; _f = _e.next()) { var otm = _f.value; - _loop_6(otm); + _loop_8(otm); } } - catch (e_4_1) { e_4 = { error: e_4_1 }; } + catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } - finally { if (e_4) throw e_4.error; } + finally { if (e_7) throw e_7.error; } } } if (promises.length > 0) { @@ -674,23 +910,23 @@ function createRemoveCheckers(schema, authDict) { try { for (var entities_1 = tslib_1.__values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) { var entity = entities_1_1.value; - _loop_3(entity); + _loop_5(entity); } } - catch (e_1_1) { e_1 = { error: e_1_1 }; } + catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) _a.call(entities_1); } - finally { if (e_1) throw e_1.error; } + finally { if (e_4) throw e_4.error; } } - var _loop_4 = function (entity) { - var e_5, _b; + var _loop_6 = function (entity) { + var e_8, _b; var cascadeRemove = authDict[entity].cascadeRemove; if (cascadeRemove) { var entitiesOnEntityAttr = []; var hasAllEntity = false; - var _loop_7 = function (attr) { + var _loop_9 = function (attr) { if (attr === '@entity') { hasAllEntity = true; return "continue"; @@ -765,13 +1001,13 @@ function createRemoveCheckers(schema, authDict) { } }; for (var attr in cascadeRemove) { - _loop_7(attr); + _loop_9(attr); } if (hasAllEntity) { var attributes = schema[entity].attributes; var ref = attributes.entity.ref; var restEntities = (0, lodash_1.difference)(ref, entitiesOnEntityAttr); - var _loop_8 = function (e) { + var _loop_10 = function (e) { checkers.push({ entity: e, action: 'remove', @@ -805,24 +1041,24 @@ function createRemoveCheckers(schema, authDict) { }); }; try { - for (var restEntities_1 = (e_5 = void 0, tslib_1.__values(restEntities)), restEntities_1_1 = restEntities_1.next(); !restEntities_1_1.done; restEntities_1_1 = restEntities_1.next()) { + for (var restEntities_1 = (e_8 = void 0, tslib_1.__values(restEntities)), restEntities_1_1 = restEntities_1.next(); !restEntities_1_1.done; restEntities_1_1 = restEntities_1.next()) { var e = restEntities_1_1.value; - _loop_8(e); + _loop_10(e); } } - catch (e_5_1) { e_5 = { error: e_5_1 }; } + catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (restEntities_1_1 && !restEntities_1_1.done && (_b = restEntities_1.return)) _b.call(restEntities_1); } - finally { if (e_5) throw e_5.error; } + finally { if (e_8) throw e_8.error; } } } } }; // 注入声明的cascade删除时的外键处理动作 for (var entity in authDict) { - _loop_4(entity); + _loop_6(entity); } return checkers; } diff --git a/lib/store/filter.js b/lib/store/filter.js index 17099ed..e903de7 100644 --- a/lib/store/filter.js +++ b/lib/store/filter.js @@ -13,7 +13,7 @@ function addFilterSegment() { } var filter = {}; filters.forEach(function (ele) { - var _a, _b, _c, _d; + var _a, _b, _c; if (ele) { for (var k in ele) { if (k === '$and') { @@ -24,25 +24,17 @@ function addFilterSegment() { filter.$and = ele[k]; } } - else if (k === '$or') { - if (filter.$or) { - (_b = filter.$or).push.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(ele[k]), false)); - } - else { - filter.$or = ele[k]; - } - } else if (filter.hasOwnProperty(k)) { if (filter.$and) { - filter.$and.push((_c = {}, - _c[k] = ele[k], - _c)); + filter.$and.push((_b = {}, + _b[k] = ele[k], + _b)); } else { filter.$and = [ - (_d = {}, - _d[k] = ele[k], - _d) + (_c = {}, + _c[k] = ele[k], + _c) ]; } } diff --git a/lib/types/Auth.d.ts b/lib/types/Auth.d.ts index b4fb5f4..6d83de8 100644 --- a/lib/types/Auth.d.ts +++ b/lib/types/Auth.d.ts @@ -1,4 +1,4 @@ -import { CascadeActionAuth, CascadeRelationAuth, ActionOnRemove } from "."; +import { CascadeActionAuth, CascadeRelationAuth, ActionOnRemove, SyncOrAsync } from "."; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption, SelectOption } from "../types/Entity"; @@ -12,21 +12,21 @@ export declare type DataChecker | Array>; - checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => any | Promise; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); + checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => SyncOrAsync; + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; export declare type RowChecker | SyncContext> = { priority?: number; type: 'row'; entity: T; action: Omit | Array>; - filter: ED[T]['Selection']['filter'] | ((operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise); + filter: ED[T]['Selection']['filter'] | ((operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => SyncOrAsync); errMsg?: string; inconsistentRows?: { entity: keyof ED; selection: (filter?: ED[T]['Selection']['filter']) => ED[keyof ED]['Selection']; }; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; export declare type RelationChecker | SyncContext> = { priority?: number; @@ -34,9 +34,9 @@ export declare type RelationChecker; - relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise; + relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => SyncOrAsync; errMsg: string; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; export declare type LogicalChecker | SyncContext> = { priority?: number; @@ -44,8 +44,8 @@ export declare type LogicalChecker; - checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => any | Promise; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => SyncOrAsync; + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; export declare type LogicalRelationChecker | SyncContext> = { priority?: number; @@ -54,7 +54,7 @@ export declare type LogicalRelationChecker; checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => any | Promise; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; export declare type Checker | SyncContext> = DataChecker | RowChecker | RelationChecker | LogicalChecker | LogicalRelationChecker; export declare type AuthDef = { diff --git a/lib/types/Exception.d.ts b/lib/types/Exception.d.ts index 56e3596..a0c875a 100644 --- a/lib/types/Exception.d.ts +++ b/lib/types/Exception.d.ts @@ -92,6 +92,12 @@ export declare class OakCongruentRowExists extends OakUserException { constructor(message?: string | undefined); } +export declare class OakPreConditionUnsetException extends OakUserException { + entity?: keyof ED; + code?: string; + constructor(message?: string | undefined, entity?: keyof ED | undefined, code?: string | undefined); + toString(): string; +} export declare function makeException(data: { name: string; message?: string; diff --git a/lib/types/Exception.js b/lib/types/Exception.js index 658b020..ecfeaee 100644 --- a/lib/types/Exception.js +++ b/lib/types/Exception.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.makeException = exports.OakDeadlock = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakRowUnexistedException = exports.OakOperExistedException = exports.OakImportDataParseException = exports.OakUniqueViolationException = exports.OakDataException = exports.OakException = void 0; +exports.makeException = exports.OakPreConditionUnsetException = exports.OakDeadlock = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakRowUnexistedException = exports.OakOperExistedException = exports.OakImportDataParseException = exports.OakUniqueViolationException = exports.OakDataException = exports.OakException = void 0; var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var OakException = /** @class */ (function (_super) { @@ -252,6 +252,25 @@ var OakDeadlock = /** @class */ (function (_super) { }(OakUserException)); exports.OakDeadlock = OakDeadlock; ; +var OakPreConditionUnsetException = /** @class */ (function (_super) { + tslib_1.__extends(OakPreConditionUnsetException, _super); + function OakPreConditionUnsetException(message, entity, code) { + var _this = _super.call(this, message || '前置条件不满足') || this; + _this.entity = entity, + _this.code = code; + return _this; + } + OakPreConditionUnsetException.prototype.toString = function () { + return JSON.stringify({ + name: this.constructor.name, + message: this.message, + code: this.code, + entity: this.entity, + }); + }; + return OakPreConditionUnsetException; +}(OakUserException)); +exports.OakPreConditionUnsetException = OakPreConditionUnsetException; function makeException(data) { var name = data.name; switch (name) { @@ -320,6 +339,11 @@ function makeException(data) { e.setOpRecords(data.opRecords); return e; } + case 'OakPreConditionUnsetException': { + var e = new OakPreConditionUnsetException(data.message, data.entity, data.code); + e.setOpRecords(data.opRecords); + return e; + } default: return; } diff --git a/lib/types/Polyfill.d.ts b/lib/types/Polyfill.d.ts index 7a2dfd8..a31122b 100644 --- a/lib/types/Polyfill.d.ts +++ b/lib/types/Polyfill.d.ts @@ -20,4 +20,5 @@ declare type IsOptional = { export declare type OptionalKeys = { [K in keyof T]: IsOptional; }[keyof T]; +export declare type SyncOrAsync = T | Promise; export {}; diff --git a/lib/types/Storage.d.ts b/lib/types/Storage.d.ts index 39883bf..946b4d2 100644 --- a/lib/types/Storage.d.ts +++ b/lib/types/Storage.d.ts @@ -26,6 +26,7 @@ export interface Attribute { notNull?: boolean; unique?: boolean; sequenceStart?: number; + enumeration?: string[]; } export declare type Attributes = Omit<{ [attrName in keyof SH]: Attribute; diff --git a/lib/utils/validator.d.ts b/lib/utils/validator.d.ts index 318c121..8c52447 100644 --- a/lib/utils/validator.d.ts +++ b/lib/utils/validator.d.ts @@ -1,3 +1,4 @@ +import { EntityDict } from "../types"; declare type ValidatorFunction = (text: string, size?: number) => string | boolean; declare type ValidatorMoneyFunction = (text: string, zero?: boolean) => string | boolean; export declare const isMobile: ValidatorFunction; @@ -18,6 +19,6 @@ export declare const isPhone: ValidatorFunction; export declare const isNumber: ValidatorFunction; export declare const isMoney: ValidatorMoneyFunction; export declare const isVehicleNumber: ValidatorFunction; -export declare function checkAttributesNotNull>(entity: string, data: T, attributes: Array, allowEmpty?: true): void; -export declare function checkAttributesScope>(entity: string, data: T, attributes: Array): void; +export declare function checkAttributesNotNull(entity: T, data: Partial, attributes: Array, allowEmpty?: true): void; +export declare function checkAttributesScope(entity: T, data: Partial, attributes: Array): void; export {}; diff --git a/package.json b/package.json index 3349451..a97f249 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.6.2", + "version": "2.6.3", "author": { "name": "XuChang" }, diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index b7d0f1a..1ff74a0 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -21,8 +21,8 @@ const Schema: Record; fulltextIndex?: true; indexes?: ts.ArrayLiteralExpression; - states: string[]; sourceFile: ts.SourceFile; + enumAttributes: Record; locale: ts.ObjectLiteralExpression; // relationHierarchy?: ts.ObjectLiteralExpression; // reverseCascadeRelationHierarchy?: ts.ObjectLiteralExpression; @@ -31,7 +31,6 @@ const Schema: Record = {}; const OneToMany: Record> = {}; @@ -265,6 +264,15 @@ function checkActionDefNameConsistent(filename: string, actionDefNode: ts.Variab assert(adfName === aName && aName === sName, `文件${filename}中的ActionDef${name.text}中ActionDef, Action和State的命名规则不一致`); } + +function checkStringLiteralLegal(filename: string, obj: string, text: string, ele: ts.TypeNode) { + assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal), `${filename}中引用的${obj} ${text}中存在不是stringliteral的类型`); + assert(!ele.literal.text.includes('$'), `${filename}中引用的action${text}中的${obj}「${ele.literal.text}」包含非法字符$`); + assert(ele.literal.text.length > 0, `${filename}中引用的action${text}中的${obj}「${ele.literal.text}」长度非法`); + assert(ele.literal.text.length < STRING_LITERAL_MAX_LENGTH, `${filename}中引用的${obj} ${text}中的「${ele.literal.text}」长度过长`); + return ele.literal.text; +} + function addActionSource(moduleName: string, name: ts.Identifier, node: ts.ImportDeclaration) { const ast = ActionAsts[moduleName]; const { moduleSpecifier } = node; @@ -308,24 +316,17 @@ function getStringTextFromUnionStringLiterals(moduleName: string, filename: stri }); } - 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) + ele => checkStringLiteralLegal(filename, 'action', (name).text, ele) ); return actions; } else { assert(ts.isLiteralTypeNode(type!), `${filename}中引用的action「${(name).text}」的定义不是union和stringLiteral类型`); - const action = getStringLiteral(type); + const action = checkStringLiteralLegal(filename, 'action', (name).text, type); return [action]; } } @@ -487,6 +488,28 @@ function checkLocaleExpressionPropertyExists(root: ts.ObjectLiteralExpression, a ) } +function getStringEnumValues(filename: string, program: ts.Program, obj: string, node: ts.TypeReferenceNode) { + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node.typeName); + let declaration = symbol?.getDeclarations()![0]!; + if (ts.isImportSpecifier(declaration)) { + const typee = checker.getDeclaredTypeOfSymbol(symbol!); + declaration = typee.aliasSymbol?.getDeclarations()![0]!; + } + + if (declaration && ts.isTypeAliasDeclaration(declaration)) { + if (ts.isUnionTypeNode(declaration.type) && ts.isLiteralTypeNode(declaration.type.types[0])) { + return declaration.type.types.map( + ele => checkStringLiteralLegal(filename, obj, (declaration).name.text, ele) + ) + } + if (ts.isLiteralTypeNode(declaration.type)) { + const value = checkStringLiteralLegal(filename, obj, declaration.name.text, declaration.type); + return [value]; + } + } +} + function analyzeEntity(filename: string, path: string, program: ts.Program, relativePath?: string) { const fullPath = `${path}/${filename}`; const sourceFile = program.getSourceFile(fullPath); @@ -509,9 +532,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela let toModi = false; let actionType = 'crud'; let _static = false; - const enumStringAttrs: string[] = []; - const states: string[] = []; - const localEnumStringTypes: string[] = []; + const enumAttributes: Record = {}; const additionalImports: ts.ImportDeclaration[] = []; let localeDef: ts.ObjectLiteralExpression | undefined = undefined; // let relationHierarchy: ts.ObjectLiteralExpression | undefined = undefined; @@ -598,8 +619,9 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela } else { schemaAttrs.push(attrNode); - if (localEnumStringTypes.includes(type.typeName.text)) { - enumStringAttrs.push((name).text); + const enumStringValues = getStringEnumValues(filename, program, '属性', type); + if (enumStringValues) { + enumAttributes[attrName] = enumStringValues; } } } @@ -628,17 +650,21 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela } else { schemaAttrs.push(attrNode); - if (ts.isUnionTypeNode(type!)) { + if (ts.isUnionTypeNode(type!) && ts.isLiteralTypeNode(type.types[0]) && ts.isStringLiteral(type.types[0].literal)) { + assert(ts.isIdentifier(name)); 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}`); - } - ) - } + const enumValues = types.map( + (ele) => checkStringLiteralLegal(filename, '属性', name.text, ele) + ); + enumAttributes[name.text] = enumValues; + } + else if (ts.isLiteralTypeNode(type!) && ts.isStringLiteral(type.literal)) { + // 单个字符串的情形,目前应该没有,没测试过,先写着 by Xc 20230221 + assert(ts.isIdentifier(name)); + const enumValues = [ + checkStringLiteralLegal(filename, '属性', name.text, type) + ]; + enumAttributes[name.text] = enumValues; } } @@ -771,18 +797,21 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela assert(!hasActionDef, `【${filename}】action定义须在Relation之后`); assert(!localeDef, `【${filename}】locale定义须在Relation之后`); // 增加userXXX对象的描述 + const relationValues = [] as string[]; 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})`); + relationValues.push(node.type.literal.text); } else { assert(ts.isUnionTypeNode(node.type), `Relation的定义只能是string类型(${filename})`); - node.type.types.forEach( + relationValues.push(...node.type.types.map( (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})`); + return ele.literal.text; } - ); + )); } const entityLc = firstLetterLowerCase(moduleName); const relationEntityName = `User${moduleName}`; @@ -819,7 +848,9 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela [relationEntityName]: { schemaAttrs: relationSchemaAttrs, sourceFile, - enumStringAttrs: ['relation'], + enumAttributes: { + relation: relationValues, + }, actionType: 'excludeUpdate', additionalImports: [ factory.createImportDeclaration( @@ -896,18 +927,6 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela 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}`); - } - ) - } } } @@ -915,62 +934,34 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela 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!; + if (declaration.type && ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === '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); */ - } - } + assert(ts.isTypeReferenceNode(actionNode)); + assert(ts.isTypeReferenceNode(stateNode)); + assert(getStringEnumValues(filename, program, 'action', (actionNode)), `文件${filename}中的action${(actionNode.typeName).text}定义不是字符串类型`); + const enumStateValues = getStringEnumValues(filename, program, 'state', stateNode); + assert(enumStateValues, `文件${filename}中的state${(stateNode.typeName).text}定义不是字符串类型`) pushStatementIntoActionAst(moduleName, node, sourceFile!); + assert(ts.isIdentifier(declaration.name)); 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)), + firstLetterLowerCase(attr), factory.createToken(ts.SyntaxKind.QuestionToken), factory.createTypeReferenceNode( - factory.createIdentifier(attr), + attr, ) ) ); - states.push(firstLetterLowerCase(attr)); + enumAttributes[firstLetterLowerCase(attr)] = enumStateValues; } else if (declaration.type && (ts.isArrayTypeNode(declaration.type!) && ts.isTypeReferenceNode(declaration.type.elementType) @@ -1115,7 +1106,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela indexes = declaration.initializer; } - else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { + else if (declaration.type && ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { // locale定义 const { type, initializer } = declaration; @@ -1124,7 +1115,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela assert(properties.length > 0, `${filename}至少需要有一种locale定义`); - const allEnumStringAttrs = enumStringAttrs.concat(states); + const allEnumStringAttrs = Object.keys(enumAttributes); const { typeArguments } = type; assert(typeArguments && ts.isTypeReferenceNode(typeArguments[0]) @@ -1170,7 +1161,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela localeDef = initializer; } - else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'Configuration') { + else if (declaration.type && 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; @@ -1223,7 +1214,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela actionType, static: _static, hasRelationDef, - enumStringAttrs: enumStringAttrs.concat(states), + enumAttributes, additionalImports, }; if (hasFulltextIndex) { @@ -5244,7 +5235,7 @@ function outputAction(outputDir: string, printer: ts.Printer) { } function constructAttributes(entity: string): ts.PropertyAssignment[] { - const { schemaAttrs, enumStringAttrs } = Schema[entity]; + const { schemaAttrs, enumAttributes } = Schema[entity]; const { [entity]: manyToOneSet } = ManyToOne; const result: ts.PropertyAssignment[] = []; @@ -5458,20 +5449,18 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { ); } else { - if (enumStringAttrs && enumStringAttrs.includes((name).text)) { + if (enumAttributes && enumAttributes[(name).text]) { attrAssignments.push( factory.createPropertyAssignment( - factory.createIdentifier("type"), - factory.createStringLiteral("varchar") + 'type', + factory.createStringLiteral("enum") ), factory.createPropertyAssignment( - factory.createIdentifier("params"), - factory.createObjectLiteralExpression( - [factory.createPropertyAssignment( - factory.createIdentifier("length"), - factory.createNumericLiteral(STRING_LITERAL_MAX_LENGTH) - )], - true + 'enumeration', + factory.createArrayLiteralExpression( + enumAttributes[(name).text].map( + ele => factory.createStringLiteral(ele) + ) ) ) ); @@ -5497,19 +5486,18 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { if (ts.isUnionTypeNode(type!)) { if (ts.isLiteralTypeNode(type.types[0])) { if (ts.isStringLiteral(type.types[0].literal)) { + assert (enumAttributes && enumAttributes[(name).text]); attrAssignments.push( factory.createPropertyAssignment( - factory.createIdentifier("type"), - factory.createStringLiteral("varchar") + 'type', + factory.createStringLiteral("enum") ), factory.createPropertyAssignment( - factory.createIdentifier("params"), - factory.createObjectLiteralExpression( - [factory.createPropertyAssignment( - factory.createIdentifier("length"), - factory.createNumericLiteral(STRING_LITERAL_MAX_LENGTH) - )], - true + 'enumeration', + factory.createArrayLiteralExpression( + enumAttributes[(name).text].map( + ele => factory.createStringLiteral(ele) + ) ) ) ); diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index a5c19f3..fe32439 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -728,28 +728,31 @@ export abstract class CascadeStore exten else { // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 - if (filter) { - if (filter.id && Object.keys(filter).length === 1) { - Object.assign(otm, { - filter: addFilterSegment({ - entity, - entityId: filter.id, - }, filterOtm), + // 暂时先封掉user上的相关更新条件,会制造出连接表上的update + if (entity !== 'user') { + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: addFilterSegment({ + entity, + entityId: filter.id, + }, filterOtm), + }); + } + else { + Object.assign(otm, { + filter: addFilterSegment({ + [entity]: filter, + }, filterOtm), + }); + } + } + if (action === 'remove' && actionOtm === 'update') { + Object.assign(dataOtm, { + entity: null, + entityId: null, }); } - else { - Object.assign(otm, { - filter: addFilterSegment({ - [entity]: filter, - }, filterOtm), - }); - } - } - if (action === 'remove' && actionOtm === 'update') { - Object.assign(dataOtm, { - entity: null, - entityId: null, - }); } } } @@ -792,20 +795,23 @@ export abstract class CascadeStore exten // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 // 绝大多数情况都是id,但也有可能update可能出现上层filter不是根据id的(userEntityGrant的过期触发的wechatQrCode的过期,见general中的userEntityGrant的trigger) - if (filter) { - if (filter.id && Object.keys(filter).length === 1) { + // 暂时先封掉user上的连接,以避免生成连接表更新 + if (entity !== 'user') { + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: addFilterSegment({ + [foreignKey]: filter.id, + }, filterOtm), + }); + } + else { Object.assign(otm, { filter: addFilterSegment({ - [foreignKey]: filter.id, + [foreignKey.slice(0, foreignKey.length - 2)]: filter, }, filterOtm), }); - } - else { - Object.assign(otm, { - filter: addFilterSegment({ - [foreignKey.slice(0, foreignKey.length - 2)]: filter, - }, filterOtm), - }); + } } } if (action === 'remove' && actionOtm === 'update') { diff --git a/src/store/checker.ts b/src/store/checker.ts index 90224c7..c3f1c81 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -3,7 +3,7 @@ import { addFilterSegment, checkFilterContains, combineFilters } from "../store/ import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; import { AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn, - EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult, REMOVE_CASCADE_PRIORITY + EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult, REMOVE_CASCADE_PRIORITY, RefOrExpression, SyncOrAsync } from "../types"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { AsyncContext } from "./AsyncRowStore"; @@ -16,13 +16,14 @@ import { generateNewId } from '../utils/uuid'; export function translateCheckerInAsyncContext< ED extends EntityDict & BaseEntityDict, + T extends keyof ED, Cxt extends AsyncContext ->(checker: Checker): { - fn: Trigger['fn']; +>(checker: Checker): { + fn: Trigger['fn']; when: 'before' | 'after'; } { - const { entity, type, action } = checker; - const when = ((action === 'create' || action instanceof Array && action.includes('create')) && ['relation'].includes(type)) ? 'after' : 'before' + const { entity, type } = checker; + const when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 switch (type) { case 'data': { const { checker: checkerFn } = checker; @@ -56,7 +57,7 @@ export function translateCheckerInAsyncContext< blockTrigger: true, }); - const e = new OakRowInconsistencyException(undefined, errMsg); + const e = new OakRowInconsistencyException(undefined, errMsg); e.addData(entity2, rows2); throw e; } @@ -73,10 +74,10 @@ export function translateCheckerInAsyncContext< const e = new OakRowInconsistencyException(undefined, errMsg); e.addData(entity, rows2); - throw e; + throw e; } } - }) as UpdateTriggerInTxn['fn']; + }) as UpdateTriggerInTxn['fn']; return { fn, when, @@ -90,29 +91,19 @@ export function translateCheckerInAsyncContext< } // assert(operation.action !== 'create', `${entity as string}上的create动作定义了relation类型的checker,请使用expressionRelation替代`); // 对后台而言,将生成的relationFilter加到filter之上(select可以在此加以权限的过滤) - if (operation.action === 'create') { - const filter2 = await relationFilter(operation, context, option); - const { data } = operation as ED[keyof ED]['Create']; - const filter = data instanceof Array ? { - id: { - $in: data.map( - ele => ele.id, - ), - }, - } : { - id: data.id, - }; - if (await checkFilterContains(entity, context, filter2, filter, true)) { - return 0; + const result = typeof relationFilter === 'function' ? await relationFilter(operation, context, option) : relationFilter; + + if (result) { + if (operation.action === 'create') { + console.warn(`${entity as string}对象的create类型的checker中,存在无法转换为表达式形式的情况,请尽量使用authDef格式定义这类checker`); + } + else { + operation.filter = combineFilters([operation.filter, result as ED[T]['Selection']['filter']]); } - throw new OakUserUnpermittedException(errMsg); - } - else { - operation.filter = combineFilters([operation.filter, await relationFilter(operation, context, option)]); } return 0; - }) as UpdateTriggerInTxn['fn']; + }) as UpdateTriggerInTxn['fn']; return { fn, when, @@ -127,7 +118,7 @@ export function translateCheckerInAsyncContext< } await checkerFn(operation, context, option); return 0; - }) as UpdateTriggerInTxn['fn']; + }) as UpdateTriggerInTxn['fn']; return { fn, when, @@ -147,8 +138,8 @@ export function translateCheckerInSyncContext< fn: (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void; when: 'before' | 'after'; } { - const { entity, type, action } = checker; - const when = ((action === 'create' || action instanceof Array && action.includes('create')) && ['relation'].includes(type)) ? 'after' : 'before' + const { entity, type } = checker; + const when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 switch (type) { case 'data': { const { checker: checkerFn } = checker; @@ -174,7 +165,7 @@ export function translateCheckerInSyncContext< return; } const e = new OakRowInconsistencyException(undefined, errMsg); - throw e; + throw e; } }; return { @@ -188,23 +179,21 @@ export function translateCheckerInSyncContext< if (context.isRoot()) { return; } - const filter2 = typeof relationFilter === 'function' ? relationFilter(operation, context, option) : relationFilter; - const { filter, action } = operation; - let filter3 = filter; - if (action === 'create') { - const { data } = operation as ED[T]['Create']; - filter3 = data instanceof Array ? { - id: { - $in: data.map(ele => ele.id), - }, - } : { id: data.id }; + const result = typeof relationFilter === 'function' ? relationFilter(operation, context, option) : relationFilter; + + assert(!(result instanceof Promise)); + if (result) { + const { filter, action } = operation; + if (action === 'create') { + console.warn(`${entity as string}对象的create类型的checker中,存在无法转换为表达式形式的情况,请尽量使用authDef格式定义这类checker`); + return; + } + assert(filter); + if (checkFilterContains(entity, context, result as ED[T]['Selection']['filter'], filter, true)) { + return; + } + throw new OakUserUnpermittedException(errMsg); } - assert(filter3); - assert(!(filter2 instanceof Promise)); - if (checkFilterContains(entity, context, filter2, filter3, true)) { - return; - } - throw new OakUserUnpermittedException(errMsg); }; return { fn, @@ -231,12 +220,22 @@ export function translateCheckerInSyncContext< } } +type FilterMakeFn = (operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'], userId: string) => ED[keyof ED]['Selection']['filter'] | { + $entity: keyof ED; + $filter?: ED[keyof ED]['Selection']['filter']; + $count?: number; +}; + function translateCascadeRelationFilterMaker( schema: StorageSchema, lch: CascadeRelationItem, - entity2: keyof ED): (userId: string) => ED[keyof ED]['Selection']['filter'] { + entity2: keyof ED, + pathPrefix?: string): FilterMakeFn { const { cascadePath, relations } = lch; - const paths = cascadePath.split('.'); + const paths = cascadePath ? cascadePath.split('.') : []; + if (pathPrefix) { + paths.unshift(pathPrefix); + } const translateRelationFilter = (entity: T): (userId: string) => ED[T]['Selection']['filter'] => { // 有两种情况,此entity和user有Relation定义,或是此entity已经指向user @@ -282,11 +281,13 @@ function translateCascadeRelationFilterMaker(entity: T, iter: number): (userId: string) => ED[T]['Selection']['filter'] => { const relation = judgeRelation(schema, entity, paths[iter]); + assert(relation === 2 || typeof relation === 'string'); + if (iter === paths.length - 1) { if (relation === 2) { - const filterMaker = translateRelationFilter(paths[iter]); + const filterMaker2 = translateRelationFilter(paths[iter]); return (userId) => { - const filter = filterMaker(userId)!; + const filter = filterMaker2(userId)!; assert(filter.id); return { entity: paths[iter], @@ -294,10 +295,9 @@ function translateCascadeRelationFilterMaker { - const filter = filterMaker(userId)!; + const filter = filterMaker2(userId)!; assert(filter.id); return { @@ -306,53 +306,265 @@ function translateCascadeRelationFilterMaker { - const subFilter = subFilterMaker(userId); - return { - [paths[iter]]: subFilter, - }; - }; - } + const filterMaker = relation === 2 ? translateFilterMakerIter(paths[iter], iter + 1) : translateFilterMakerIter(relation, iter + 1); return (userId) => ({ - [paths[iter]]: subFilterMaker(userId), + [paths[iter]]: filterMaker(userId), }); } }; - const filter = cascadePath ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2); - return filter; + const filterMaker = paths.length ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2); + if (!paths.length) { + return (oper, userId) => filterMaker(userId); + } + /** + * 针对第一层做一下特别优化,比如对象A指向对象B(多对一),如果A的cascadePath是 'B', + * 当create A时,会带有Bid。此时生成该B对象上的相关表达式查询返回,可以避免必须将此判定在对象创建之后再做 + * 另一使用场景是,在查询A时,如果带有Bid(在对象跳一对多子对象场景下很常见),可以提前判定这个查询对某些用户一定返回空集 + */ + const [attr] = paths; + const relation = judgeRelation(schema, entity2, attr); + assert(relation === 2 || typeof relation === 'string'); + const filterMaker2 = paths.length > 1 + ? (relation === 2 ? translateFilterMakerIter(attr, 1) : translateFilterMakerIter(relation, 1)) + : (relation === 2 ? translateRelationFilter(attr) : translateRelationFilter(relation)); + return (operation, userId) => { + const { action } = operation as ED[keyof ED]['Operation']; + if (action === 'create') { + const { data } = operation as ED[keyof ED]['Create']; + const getForeignKeyId = (d: ED[keyof ED]['CreateSingle']['data']) => { + if (relation === 2) { + if (d.entity === attr && typeof d.entityId === 'string') { + return d.entitId as string; + } + throw new OakUserUnpermittedException(); + } + else { + assert(typeof relation === 'string'); + if (typeof d[`${attr}Id`] === 'string') { + return d[`${attr}Id`] as string; + } + throw new OakUserUnpermittedException(); + } + }; + if (relation === 2) { + if (data instanceof Array) { + const fkIds = uniq(data.map(d => getForeignKeyId(d))); + return { + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }), + $count: fkIds.length, + }; + } + const fkId = getForeignKeyId(data); + return { + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: fkId }), + }; + } + assert(typeof relation === 'string'); + if (data instanceof Array) { + const fkIds = uniq(data.map(d => getForeignKeyId(d))); + return { + $entity: relation, + $filter: addFilterSegment(filterMaker2(userId), { id: { $in: fkIds } }), + $count: fkIds.length, + }; + } + const fkId = getForeignKeyId(data); + return { + $entity: relation, + $filter: addFilterSegment(filterMaker2(userId), { id: fkId }), + }; + } + const { filter } = operation; + if (relation === 2 && filter?.entity === attr && filter?.entityId) { + if (typeof filter.entityId === 'string') { + return { + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: filter.entityId }), + }; + } + else if (filter.entityId.$in && filter.entityId.$in instanceof Array) { + const entityIds = uniq(filter.entityId.$in); + return { + $entity: relation, + $filter: addFilterSegment(filterMaker2(userId), { id: { $in: entityIds } }), + $count: entityIds.length, + }; + } + } + else if (filter && filter[`${attr}Id`]) { + if (typeof filter[`${attr}Id`] === 'string') { + return { + $entity: attr, + $filter: addFilterSegment(filterMaker2(userId), { id: filter[`${attr}Id`] }), + }; + } + else if (filter[`${attr}Id`].$in && filter[`${attr}Id`].$in instanceof Array) { + const entityIds = uniq(filter[`${attr}Id`].$in); + return { + $entity: relation, + $filter: addFilterSegment(filterMaker2(userId), { id: { $in: entityIds } }), + $count: entityIds.length, + }; + } + } + return filterMaker(userId); + }; } function translateActionAuthFilterMaker( schema: StorageSchema, relationItem: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[], - entity: keyof ED -): (userId: string) => ED[keyof ED]['Selection']['filter'] { + entity: keyof ED, + pathPrefix?: string, +): FilterMakeFn | (FilterMakeFn | FilterMakeFn[])[] { if (relationItem instanceof Array) { const maker = relationItem.map( ele => { if (ele instanceof Array) { return ele.map( - ele2 => translateCascadeRelationFilterMaker(schema, ele2, entity) + ele2 => translateCascadeRelationFilterMaker(schema, ele2, entity, pathPrefix) ); } - return [translateCascadeRelationFilterMaker(schema, ele, entity)]; + return translateCascadeRelationFilterMaker(schema, ele, entity, pathPrefix); } ); - return (userId) => ({ - $or: maker.map( - ele => ({ - $and: ele.map( - ele2 => ele2(userId)! - ) - }) - ) - }) + return maker; } - const filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity); - return (userId) => filterMaker(userId); + const filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity, pathPrefix); + return filterMaker; +} + +function makePotentialFilter | SyncContext> ( + operation: ED[keyof ED]['Operation'] | ED[keyof ED]['Selection'], + context: Cxt, + filterMaker: FilterMakeFn | (FilterMakeFn | FilterMakeFn[])[]): SyncOrAsync { + const userId = context.getCurrentUserId(); + assert(userId!); + const filters = filterMaker instanceof Array ? filterMaker.map( + ele => { + if (ele instanceof Array) { + return ele.map( + ele2 => ele2(operation, userId), + ); + } + return ele(operation, userId); + } + ) : [filterMaker(operation, userId)]; + + /** + * 在下面的逻辑中,如果某个maker返回的是$entity类型,则检查是否有满足条件的项,没有就要抛出异常,有就返回undefined + * undefined项即意味着该条件通过 + * 再加上and和or的布尔逻辑判断,得到最终结果 + * 还要考虑同步和异步…… + * 代码比较复杂,因为原先没有$entity这种返回结果的设计 + * by Xc 20130219 + */ + const filtersOr: (SyncOrAsync>)[] = []; + let isAsyncOr = false; + for (const f of filters) { + if (f instanceof Array) { + let isAsyncAnd = true; + const filtersAnd: (SyncOrAsync>)[] = []; + for (const ff of f) { + if (ff?.$entity) { + const { $entity, $filter, $count = 1 } = ff!; + const count = context.count($entity, { + filter: $filter, + }, {}); + if (count instanceof Promise) { + isAsyncAnd = true; + filtersAnd.push( + count.then( + (c2) => { + if (c2 >= $count) { + return undefined; + } + return new OakUserUnpermittedException(); + } + ) + ); + } + else { + filtersAnd.push(count >= $count ? undefined : new OakUserUnpermittedException()); + } + } + else if (ff) { + filtersAnd.push(ff as ED[keyof ED]['Selection']['filter']); + } + } + if (isAsyncAnd = true) { + isAsyncOr = true; + filtersOr.push(isAsyncAnd ? Promise.all(filtersAnd).then( + (fa) => { + const faR: ED[keyof ED]['Selection']['filter'][] = []; + for (const faItem of fa) { + if (faItem instanceof OakUserUnpermittedException) { + return faItem; + } + else if (faItem) { + faR.push(faItem); + } + } + if (faR.length > 0) { + return { + $and: faR, + }; + } + } + ) : ({ + $and: filtersAnd, + } as ED[keyof ED]['Selection']['filter'])); + } + } + else { + if (f?.$entity) { + const { $entity, $filter, $count = 1 } = f!; + const count = context.count($entity, { + filter: $filter, + }, {}); + if (count instanceof Promise) { + isAsyncOr = true; + filtersOr.push( + count.then( + (c2) => c2 >= $count ? undefined : new OakUserUnpermittedException() + ) + ); + } + else { + filtersOr.push(count >= $count ? undefined : new OakUserUnpermittedException()); + } + } + else if (f) { + filtersOr.push(f as ED[keyof ED]['Selection']['filter']); + } + } + } + + // or的逻辑是,有一个成功就直接通过 + const returnOrFilters = (filters: (ED[keyof ED]['Selection']['filter'] | OakUserUnpermittedException)[]) => { + if (filters.length === 0 || filters.includes(undefined)) { + return undefined; + } + const foFilters = filters.filter( + ele => ele !== undefined && !(ele instanceof OakUserUnpermittedException) + ); + if (foFilters.length > 0) { + return { + $or: foFilters, + }; + } + throw new OakUserUnpermittedException(); + }; + if (isAsyncOr) { + return Promise.all(filtersOr) + .then( + (filters) => returnOrFilters(filters) + ); + } + return returnOrFilters(filtersOr); } /** @@ -370,11 +582,11 @@ export function createAuthCheckers ED[keyof ED]['Selection']['filter']>; + const raFilterMakerDict = {} as Record | (FilterMakeFn | FilterMakeFn[])[]>; const userEntityName = `user${firstLetterUpperCase(entity)}`; for (const r in relationAuth) { Object.assign(raFilterMakerDict, { - [r]: translateActionAuthFilterMaker(schema, relationAuth[r as NonNullable]!, entity), + [r]: translateActionAuthFilterMaker(schema, relationAuth[r as NonNullable]!, userEntityName, entity), }); } const entityIdAttr = `${entity}Id`; @@ -386,14 +598,12 @@ export function createAuthCheckers { - const userId = context.getCurrentUserId(); + // 目前过不去 + return undefined; + /* const userId = context.getCurrentUserId(); const { filter } = operation as ED[keyof ED]['Remove']; - const makeFilterFromRows = (rows: Partial[]) => { + const makeFilterFromRows = (rows: Partial[]): SyncOrAsync => { const relations = uniq(rows.map(ele => ele.relation)); const entityIds = uniq(rows.map(ele => ele[entityIdAttr])); assert(entityIds.length === 1, `在回收${userEntityName}上权限时,单次回收涉及到了不同的对象,此操作不被允许`); // const entityId = entityIds[0]!; // 所有的relation条件要同时满足and关系(注意这里的filter翻译出来是在entity对象上,不是在userEntity对象上) - return { - $and: relations.map( - (relation) => raFilterMakerDict[relation!] - ).filter( - ele => !!ele - ).map( - ele => ({ - [entity]: ele(userId!), - }) - ) - } as ED[keyof ED]['Selection']['filter']; + const filtersAnd = relations.map( + (relation) => raFilterMakerDict[relation!] + ).filter( + ele => !!ele + ).map( + ele => makePotentialFilter(operation, context, ele) + ); + if (filtersAnd.find(ele => ele instanceof Promise)) { + return Promise.all(filtersAnd).then( + (fa) => { + if (fa.length > 0) { + return { + $and: fa, + } as ED[keyof ED]['Selection']['filter']; + } + } + ); + } + if (filtersAnd.length > 0) { + return { + $and: filtersAnd + } as ED[keyof ED]['Selection']['filter']; + } }; const toBeRemoved = context.select(userEntityName, { @@ -438,7 +662,7 @@ export function createAuthCheckers makeFilterFromRows(rows) ); } - return makeFilterFromRows(toBeRemoved); + return makeFilterFromRows(toBeRemoved); */ }, errMsg: '越权操作', }); @@ -455,7 +679,7 @@ export function createAuthCheckers { // const { filter } = operation; - const filter = filterMaker(context.getCurrentUserId()!); + const filter = makePotentialFilter(operation, context, filterMaker); return filter; }, errMsg: '定义的actionAuth中检查出来越权操作', @@ -538,7 +762,7 @@ export function createRemoveCheckers { if (row) { - const e = new OakRowInconsistencyException(undefined, `您无法删除存在有效数据「${otm as string}」关联的行`); + const e = new OakRowInconsistencyException(undefined, `您无法删除存在有效数据「${otm as string}」关联的行`); e.addData(otm, [row]); throw e; } @@ -614,7 +838,7 @@ export function createRemoveCheckers 0) { return Promise.all(promises).then( @@ -653,7 +877,7 @@ export function createRemoveCheckers(...f filter.$and = ele[k]; } } - else if (k === '$or') { - if (filter.$or) { - filter.$or.push(...(ele[k] as any)); - } - else { - filter.$or = ele[k]; - } - } else if (filter.hasOwnProperty(k)) { if (filter.$and) { filter.$and.push({ diff --git a/src/types/Auth.ts b/src/types/Auth.ts index 048f049..88ed328 100644 --- a/src/types/Auth.ts +++ b/src/types/Auth.ts @@ -1,4 +1,4 @@ -import { CascadeActionAuth, RelationHierarchy, CascadeRelationAuth, ActionOnRemove } from "."; +import { CascadeActionAuth, RelationHierarchy, CascadeRelationAuth, ActionOnRemove, SyncOrAsync } from "."; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption, SelectOption } from "../types/Entity"; @@ -15,9 +15,9 @@ export type DataChecker | Array>; - checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => any | Promise; + checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => SyncOrAsync; conditionalFilter?: ED[T]['Update']['filter'] | ( - (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise + (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync ); }; @@ -27,7 +27,7 @@ export type RowChecker | Array>; filter: ED[T]['Selection']['filter'] | ( - (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise + (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => SyncOrAsync ); // 对行的额外检查条件 errMsg?: string; inconsistentRows?: { // 因为这里的限制不一定在本row上,如果不传这个exception,则默认返回本row上的exception @@ -35,7 +35,7 @@ export type RowChecker ED[keyof ED]['Selection']; }; conditionalFilter?: ED[T]['Update']['filter'] | ( - (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise + (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync ); }; @@ -45,10 +45,10 @@ export type RelationChecker; - relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise; // 生成一个额外的relation相关的filter,加在原先的filter上 + relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => SyncOrAsync, // 生成一个额外的relation相关的filter,加在原先的filter上 errMsg: string; conditionalFilter?: ED[T]['Update']['filter'] | ( - (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise + (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync ); }; @@ -62,8 +62,8 @@ export type LogicalChecker any | Promise; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + ) => SyncOrAsync; + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; export type LogicalRelationChecker | SyncContext> = { @@ -77,7 +77,7 @@ export type LogicalRelationChecker any | Promise; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => SyncOrAsync); }; diff --git a/src/types/Exception.ts b/src/types/Exception.ts index 289b87b..69bcbec 100644 --- a/src/types/Exception.ts +++ b/src/types/Exception.ts @@ -230,6 +230,27 @@ export class OakDeadlock extends OakUserException { } }; +export class OakPreConditionUnsetException extends OakUserException { + entity?: keyof ED; + code?: string; + + constructor(message?: string | undefined, entity?: keyof ED | undefined, code?: string | undefined) { + super(message || '前置条件不满足'); + this.entity = entity, + this.code = code; + } + + + toString(): string { + return JSON.stringify({ + name: this.constructor.name, + message: this.message, + code: this.code, + entity: this.entity, + }); + } +} + export function makeException(data: { name: string; message?: string; @@ -303,6 +324,11 @@ export function makeException(data: { e.setOpRecords(data.opRecords); return e; } + case 'OakPreConditionUnsetException': { + const e = new OakPreConditionUnsetException(data.message, data.entity, data.code); + e.setOpRecords(data.opRecords); + return e; + } default: return; } diff --git a/src/types/Polyfill.ts b/src/types/Polyfill.ts index 743366f..9312f8e 100644 --- a/src/types/Polyfill.ts +++ b/src/types/Polyfill.ts @@ -17,4 +17,7 @@ export type OneOf = ValueOf>; // 判断对象中的optional key type IsOptional = { [K1 in Exclude]: T[K1] } & { K?: T[K] } extends T ? K : never -export type OptionalKeys = { [K in keyof T]: IsOptional }[keyof T] \ No newline at end of file +export type OptionalKeys = { [K in keyof T]: IsOptional }[keyof T] + +// 同或异步返回 +export type SyncOrAsync = T | Promise; \ No newline at end of file diff --git a/src/types/Storage.ts b/src/types/Storage.ts index 84190a3..f41d84f 100644 --- a/src/types/Storage.ts +++ b/src/types/Storage.ts @@ -33,6 +33,7 @@ export interface Attribute { notNull?: boolean; unique?: boolean; sequenceStart?: number; + enumeration?: string[]; } export type Attributes = Omit<{ diff --git a/src/utils/validator.ts b/src/utils/validator.ts index c82eab6..b05cb04 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -3,7 +3,7 @@ */ 'use strict'; -import { OakInputIllegalException } from "../types"; +import { EntityDict, OakInputIllegalException } from "../types"; type ValidatorFunction = (text: string, size?:number) => string|boolean; type ValidatorMoneyFunction = (text: string, zero?:boolean) => string|boolean; @@ -106,7 +106,7 @@ export const isVehicleNumber: ValidatorFunction = (str) => { } -export function checkAttributesNotNull>(entity: string, data: T, attributes: Array, allowEmpty?: true) { +export function checkAttributesNotNull(entity: T, data: Partial, attributes: Array, allowEmpty?: true) { const attrs = attributes.filter( (attr) => { if (data[attr] === null || data[attr] === ''|| data[attr] === undefined) { @@ -119,16 +119,16 @@ export function checkAttributesNotNull>(entity: st ) as string[]; if (attrs.length > 0) { - throw new OakInputIllegalException(entity, attrs, '属性不能为空'); + throw new OakInputIllegalException(entity as string, attrs, '属性不能为空'); } }; -export function checkAttributesScope>(entity: string, data: T, attributes: Array) { +export function checkAttributesScope(entity: T, data: Partial, attributes: Array) { const attrs = attributes.filter( attr => !data.hasOwnProperty(attr) ) as string[]; if (attrs.length > 0) { - throw new OakInputIllegalException(entity, attrs, '多余的属性'); + throw new OakInputIllegalException(entity as string, attrs, '多余的属性'); } } \ No newline at end of file