diff --git a/lib/actions/relation.d.ts b/lib/actions/relation.d.ts index e5081a1..d012b77 100644 --- a/lib/actions/relation.d.ts +++ b/lib/actions/relation.d.ts @@ -1 +1,5 @@ +import { CascadeRelationItem, RelationHierarchy } from "../types/Entity"; export declare type GenericRelation = 'owner'; +export declare function convertHierarchyToAuth(hierarchy: RelationHierarchy): { + [K in R]?: CascadeRelationItem; +}; diff --git a/lib/actions/relation.js b/lib/actions/relation.js index c8ad2e5..174f3fe 100644 --- a/lib/actions/relation.js +++ b/lib/actions/relation.js @@ -1,2 +1,38 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.convertHierarchyToAuth = void 0; +var tslib_1 = require("tslib"); +function convertHierarchyToAuth(hierarchy) { + var e_1, _a; + var _b; + var reverseHierarchy = {}; + for (var r in hierarchy) { + try { + for (var _c = (e_1 = void 0, tslib_1.__values(hierarchy[r])), _d = _c.next(); !_d.done; _d = _c.next()) { + var r2 = _d.value; + if (reverseHierarchy[r2]) { + (_b = reverseHierarchy[r2]) === null || _b === void 0 ? void 0 : _b.push(r); + } + else { + reverseHierarchy[r2] = [r]; + } + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (_d && !_d.done && (_a = _c.return)) _a.call(_c); + } + finally { if (e_1) throw e_1.error; } + } + } + var result = {}; + for (var r in reverseHierarchy) { + result[r] = { + cascadePath: '', + relations: reverseHierarchy[r], + }; + } + return result; +} +exports.convertHierarchyToAuth = convertHierarchyToAuth; diff --git a/lib/base-app-domain/Modi/Schema.d.ts b/lib/base-app-domain/Modi/Schema.d.ts index dd96db2..d9d6dac 100644 --- a/lib/base-app-domain/Modi/Schema.d.ts +++ b/lib/base-app-domain/Modi/Schema.d.ts @@ -107,7 +107,7 @@ export declare type SortNode = { export declare type Sorter = SortNode[]; export declare type SelectOperation

= Omit, "id">; export declare type Selection

= Omit, "action">; -export declare type Aggregation = Omit, "id">; +export declare type Aggregation = Omit, "id">; export declare type CreateOperationData = FormCreateData> & ({ entity?: string; entityId?: string; @@ -127,10 +127,8 @@ export declare type UpdateOperationData = FormUpdateData & { export declare type UpdateOperation = OakOperation<"update" | ParticularAction | string, UpdateOperationData, Filter, Sorter>; export declare type RemoveOperationData = {}; export declare type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; -export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation; +export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation; export declare type ModiIdSubQuery = Selection; -export declare type NativeAttr = OpAttr; -export declare type FullAttr = NativeAttr | `modiEntitys$${number}.${ModiEntity.NativeAttr}` | `operEntitys$${number}.${OperEntity.NativeAttr}`; export declare type EntityDef = { Schema: Schema; OpSchema: OpSchema; diff --git a/lib/base-app-domain/ModiEntity/Schema.d.ts b/lib/base-app-domain/ModiEntity/Schema.d.ts index b09482d..c3c0bee 100644 --- a/lib/base-app-domain/ModiEntity/Schema.d.ts +++ b/lib/base-app-domain/ModiEntity/Schema.d.ts @@ -83,7 +83,7 @@ export declare type SortNode = { export declare type Sorter = SortNode[]; export declare type SelectOperation

= Omit, "id">; export declare type Selection

= Omit, "action">; -export declare type Aggregation = Omit, "id">; +export declare type Aggregation = Omit, "id">; export declare type CreateOperationData = FormCreateData> & (({ modiId?: never; modi: Modi.CreateSingleOperation; @@ -142,12 +142,10 @@ export declare type RemoveOperationData = {} & (({ [k: string]: any; }); export declare type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; -export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation; +export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation; export declare type ModiIdSubQuery = Selection; export declare type UserIdSubQuery = Selection; export declare type ModiEntityIdSubQuery = Selection; -export declare type NativeAttr = OpAttr | `modi.${Modi.NativeAttr}` | `entity.${User.NativeAttr}`; -export declare type FullAttr = NativeAttr; export declare type EntityDef = { Schema: Schema; OpSchema: OpSchema; diff --git a/lib/base-app-domain/Oper/Schema.d.ts b/lib/base-app-domain/Oper/Schema.d.ts index d8d1e5b..fde88b9 100644 --- a/lib/base-app-domain/Oper/Schema.d.ts +++ b/lib/base-app-domain/Oper/Schema.d.ts @@ -89,7 +89,7 @@ export declare type SortNode = { export declare type Sorter = SortNode[]; export declare type SelectOperation

= Omit, "id">; export declare type Selection

= Omit, "action">; -export declare type Aggregation = Omit, "id">; +export declare type Aggregation = Omit, "id">; export declare type CreateOperationData = FormCreateData> & (({ operatorId?: never; operator?: User.CreateSingleOperation; @@ -125,11 +125,9 @@ export declare type RemoveOperationData = {} & (({ operator?: User.UpdateOperation | User.RemoveOperation; })); export declare type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; -export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation; +export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation; export declare type UserIdSubQuery = Selection; export declare type OperIdSubQuery = Selection; -export declare type NativeAttr = OpAttr | `operator.${User.NativeAttr}`; -export declare type FullAttr = NativeAttr | `operEntitys$${number}.${OperEntity.NativeAttr}`; export declare type EntityDef = { Schema: Schema; OpSchema: OpSchema; diff --git a/lib/base-app-domain/OperEntity/Schema.d.ts b/lib/base-app-domain/OperEntity/Schema.d.ts index f23f394..98d2fc9 100644 --- a/lib/base-app-domain/OperEntity/Schema.d.ts +++ b/lib/base-app-domain/OperEntity/Schema.d.ts @@ -92,7 +92,7 @@ export declare type SortNode = { export declare type Sorter = SortNode[]; export declare type SelectOperation

= Omit, "id">; export declare type Selection

= Omit, "action">; -export declare type Aggregation = Omit, "id">; +export declare type Aggregation = Omit, "id">; export declare type CreateOperationData = FormCreateData> & (({ operId?: never; oper: Oper.CreateSingleOperation; @@ -157,13 +157,11 @@ export declare type RemoveOperationData = {} & ({ [k: string]: any; }); export declare type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; -export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation; +export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation; export declare type OperIdSubQuery = Selection; export declare type ModiIdSubQuery = Selection; export declare type UserIdSubQuery = Selection; export declare type OperEntityIdSubQuery = Selection; -export declare type NativeAttr = OpAttr | `oper.${Oper.NativeAttr}` | `entity.${Modi.NativeAttr}` | `entity.${User.NativeAttr}`; -export declare type FullAttr = NativeAttr; export declare type EntityDef = { Schema: Schema; OpSchema: OpSchema; diff --git a/lib/base-app-domain/User/Schema.d.ts b/lib/base-app-domain/User/Schema.d.ts index c747fbc..29fc007 100644 --- a/lib/base-app-domain/User/Schema.d.ts +++ b/lib/base-app-domain/User/Schema.d.ts @@ -92,7 +92,7 @@ export declare type SortNode = { export declare type Sorter = SortNode[]; export declare type SelectOperation

= Omit, "id">; export declare type Selection

= Omit, "action">; -export declare type Aggregation = Omit, "id">; +export declare type Aggregation = Omit, "id">; export declare type CreateOperationData = FormCreateData & { oper$operator?: OakOperation<"create", Omit[]> | Array>>; operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; @@ -110,10 +110,8 @@ export declare type UpdateOperationData = FormUpdateData & { export declare type UpdateOperation = OakOperation<"update" | RelationAction | string, UpdateOperationData, Filter, Sorter>; export declare type RemoveOperationData = {}; export declare type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; -export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation | SelectOperation; +export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation; export declare type UserIdSubQuery = Selection; -export declare type NativeAttr = OpAttr; -export declare type FullAttr = NativeAttr | `opers$${number}.${Oper.NativeAttr}` | `operEntitys$${number}.${OperEntity.NativeAttr}` | `modiEntitys$${number}.${ModiEntity.NativeAttr}`; export declare type EntityDef = { Schema: Schema; OpSchema: OpSchema; diff --git a/lib/checkers/index.d.ts b/lib/checkers/index.d.ts index c5b376c..38ec478 100644 --- a/lib/checkers/index.d.ts +++ b/lib/checkers/index.d.ts @@ -1,5 +1,5 @@ import { EntityDict } from '../base-app-domain'; import { AsyncContext } from '../store/AsyncRowStore'; import { SyncContext } from '../store/SyncRowStore'; -import { StorageSchema, EntityDict as BaseEntityDict, Checker } from '../types'; -export declare function createDynamicCheckers | SyncContext>(schema: StorageSchema): Checker[]; +import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDefDict } from '../types'; +export declare function createDynamicCheckers | SyncContext>(schema: StorageSchema, authDict?: AuthDefDict): Checker[]; diff --git a/lib/checkers/index.js b/lib/checkers/index.js index 428ed0d..3c7f105 100644 --- a/lib/checkers/index.js +++ b/lib/checkers/index.js @@ -4,10 +4,12 @@ exports.createDynamicCheckers = void 0; var tslib_1 = require("tslib"); var checker_1 = require("../store/checker"); var modi_1 = require("../store/modi"); -function createDynamicCheckers(schema) { +function createDynamicCheckers(schema, authDict) { var checkers = []; checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, modi_1.createModiRelatedCheckers)(schema)), false)); - checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createRelationHierarchyCheckers)(schema)), false)); + if (authDict) { + checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createAuthCheckers)(schema, authDict)), false)); + } return checkers; } exports.createDynamicCheckers = createDynamicCheckers; diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index 42402a4..2a3d8b2 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -337,7 +337,8 @@ function analyzeEntity(filename, path, program, relativePath) { var localEnumStringTypes = []; var additionalImports = []; var localeDef = undefined; - var relationHierarchy = undefined; + // let relationHierarchy: ts.ObjectLiteralExpression | undefined = undefined; + // let reverseCascadeRelationHierarchy: ts.ObjectLiteralExpression | undefined = undefined; ts.forEachChild(sourceFile, function (node) { var _a, _b, _c, _d; if (ts.isImportDeclaration(node)) { @@ -795,15 +796,22 @@ function analyzeEntity(filename, path, program, relativePath) { _static = true; // static如果有值只能为true } } - else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'RelationHierarchy') { + /* else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'RelationHierarchy') { // RelationHierary - (0, assert_1.default)(hasRelationDef, "".concat(moduleName, "\u4E2D\u7684Relation\u5B9A\u4E49\u5728RelationHierarchy\u4E4B\u540E")); - var initializer = declaration.initializer; - (0, assert_1.default)(ts.isObjectLiteralExpression(initializer), "".concat(moduleName, "\u4E2D\u7684RelationHierarchy\u7684\u5B9A\u4E49\u5FC5\u987B\u662F\u521D\u59CB\u5316\u4E3AObjectLiteralExpress")); + assert(hasRelationDef, `${moduleName}中的Relation定义在RelationHierarchy之后`); + const { initializer } = declaration; + assert(ts.isObjectLiteralExpression(initializer!), `${moduleName}中的RelationHierarchy的定义必须是初始化为ObjectLiteralExpress`); relationHierarchy = initializer; } + else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ReverseCascadeRelationHierarchy') { + // ReverseCascadeRelationHierarchy + assert(hasRelationDef, `${moduleName}中的Relation定义在ReverseCascadeRelationHierarchy之后`); + const { initializer } = declaration; + assert(ts.isObjectLiteralExpression(initializer!), `${moduleName}中的RelationHierarchy的定义必须是初始化为ObjectLiteralExpress`); + reverseCascadeRelationHierarchy = initializer; + } */ else { - throw new Error("".concat(moduleName, "\uFF1A\u4E0D\u80FD\u7406\u89E3\u7684\u5B9A\u4E49\u5185\u5BB9").concat(declaration.name.getText())); + throw new Error("".concat(moduleName, "\uFF1A\u4E0D\u80FD\u7406\u89E3\u7684\u5B9A\u4E49\u5185\u5BB9").concat(declaration.name.text)); } }); } @@ -843,15 +851,25 @@ function analyzeEntity(filename, path, program, relativePath) { locale: localeDef, }); } - if (hasRelationDef) { - (0, assert_1.default)(relationHierarchy, "".concat(filename, "\u4E2D\u7F3A\u5C11\u4E86relationHierarchy\u5B9A\u4E49")); - (0, lodash_1.assign)(schema, { - relationHierarchy: relationHierarchy, - }); + /* if (hasRelationDef) { + if(!relationHierarchy && !reverseCascadeRelationHierarchy){ + console.warn(`${filename}中定义了Relation,但并没有relationHierarchy或reverseCascadeRelationHierarchy的定义,请注意自主编写权限分配的checker`); + } + if (relationHierarchy) { + assign(schema, { + relationHierarchy, + }); + } + if (reverseCascadeRelationHierarchy) { + assign(schema, { + reverseCascadeRelationHierarchy, + }); + } } else { - (0, assert_1.default)(!relationHierarchy, "".concat(filename, "\u4E2D\u5177\u6709relationHierarchy\u5B9A\u4E49\u4F46\u6CA1\u6709Relation\u5B9A\u4E49")); - } + assert(!relationHierarchy, `${filename}中具有relationHierarchy定义但没有Relation定义`); + assert(!reverseCascadeRelationHierarchy, `${filename}中具有reverseCascadeRelationHierarchy定义但没有Relation定义`) + } */ (0, lodash_1.assign)(Schema, (_a = {}, _a[moduleName] = schema, _a)); @@ -1783,7 +1801,6 @@ function constructActions(statements, entity) { factory.createLiteralTypeNode(factory.createStringLiteral("action")) ])), factory.createTypeAliasDeclaration(undefined, [factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Aggregation"), undefined, factory.createTypeReferenceNode(factory.createIdentifier("Omit"), [ factory.createTypeReferenceNode(factory.createIdentifier("DeduceAggregation"), [ - factory.createTypeReferenceNode(factory.createIdentifier("Schema"), undefined), factory.createTypeReferenceNode(factory.createIdentifier("Projection"), undefined), factory.createTypeReferenceNode(factory.createIdentifier("Filter"), undefined), factory.createTypeReferenceNode(factory.createIdentifier("Sorter"), undefined) @@ -1939,6 +1956,11 @@ function constructActions(statements, entity) { var reverseOneNodes = []; if (ReversePointerEntities[entity]) { if (ReversePointerRelations[entity]) { + var schemaAttrs = Schema[entity].schemaAttrs; + var questionToken = schemaAttrs.find(function (ele) { + var name = ele.name; + return name.text === 'entity'; + }).questionToken; try { for (var _q = tslib_1.__values(ReversePointerRelations[entity]), _r = _q.next(); !_r.done; _r = _q.next()) { var one = _r.value; @@ -1956,10 +1978,8 @@ function constructActions(statements, entity) { factory.createPropertySignature(undefined, factory.createIdentifier((0, string_1.firstLetterLowerCase)(one)), undefined, factory.createTypeReferenceNode(createForeignRef(entity, one, 'UpdateOperation'))) ]); var noCascadeNode = factory.createTypeLiteralNode([ - factory.createPropertySignature(undefined, factory.createIdentifier('entity'), undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc - factory.createLiteralTypeNode(factory.createStringLiteral("".concat((0, string_1.firstLetterLowerCase)(one))))), - factory.createPropertySignature(undefined, factory.createIdentifier('entityId'), undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc - factory.createTypeReferenceNode(factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))])) + factory.createPropertySignature(undefined, factory.createIdentifier('entity'), questionToken, factory.createLiteralTypeNode(factory.createStringLiteral("".concat((0, string_1.firstLetterLowerCase)(one))))), + factory.createPropertySignature(undefined, factory.createIdentifier('entityId'), questionToken, factory.createTypeReferenceNode(factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))])) ]); if (Schema[one].static) { reverseOneNodes.push(noCascadeNode); @@ -2584,8 +2604,7 @@ function constructActions(statements, entity) { statements.push(factory.createTypeAliasDeclaration(undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("Operation"), undefined, factory.createUnionTypeNode([ factory.createTypeReferenceNode(factory.createIdentifier("CreateOperation"), undefined), factory.createTypeReferenceNode(factory.createIdentifier("UpdateOperation"), undefined), - factory.createTypeReferenceNode(factory.createIdentifier("RemoveOperation"), undefined), - factory.createTypeReferenceNode(factory.createIdentifier("SelectOperation"), undefined) + factory.createTypeReferenceNode(factory.createIdentifier("RemoveOperation"), undefined) ]))); } var initialStatements = function () { return [ @@ -2642,7 +2661,6 @@ var initialStatements = function () { return [ function outputSubQuery(outputDir, printer) { var statements = []; if (process.env.COMPLING_AS_LIB) { - statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("Selection"))])), factory.createStringLiteral((0, env_1.TYPE_PATH_IN_OAK_DOMAIN)(1)), undefined)); } for (var entity in Schema) { // import * as User from '../User/Schema'; @@ -2755,7 +2773,8 @@ function outputSchema(outputDir, printer) { constructSorter(statements, entity); constructActions(statements, entity); constructQuery(statements, entity); - constructFullAttrs(statements, entity); + // 现在FullAttrs和NativeAttrs似乎没什么用,还会引起递归 + // constructFullAttrs(statements, entity); var makeActionArguments = []; if (ActionAsts[entity]) { makeActionArguments.push(factory.createTypeReferenceNode('Action')); @@ -2787,6 +2806,9 @@ function outputSchema(outputDir, printer) { if (ActionAsts[entity]) { EntityDefAttrs.push(factory.createPropertySignature(undefined, factory.createIdentifier("ParticularAction"), undefined, factory.createTypeReferenceNode(factory.createIdentifier('ParticularAction'), undefined))); } + if (typeof Schema[entity].hasRelationDef === 'object' && ts.isTypeAliasDeclaration(Schema[entity].hasRelationDef)) { + EntityDefAttrs.push(factory.createPropertySignature(undefined, factory.createIdentifier("Relation"), undefined, factory.createTypeReferenceNode(factory.createIdentifier('Relation'), undefined))); + } statements.push(factory.createTypeAliasDeclaration(undefined, [factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier("EntityDef"), undefined, factory.createTypeLiteralNode(EntityDefAttrs))); var result = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements), Schema[entity].sourceFile); var fileName = path_1.default.join(outputDir, entity, 'Schema.ts'); @@ -3045,13 +3067,19 @@ function outputStorage(outputDir, printer) { var entityAssignments = []; for (var entity in Schema) { var indexExpressions = []; - var _a = Schema[entity], sourceFile = _a.sourceFile, inModi = _a.inModi, indexes = _a.indexes, toModi = _a.toModi, actionType = _a.actionType, _static = _a.static, relationHierarchy = _a.relationHierarchy; + var _a = Schema[entity], sourceFile = _a.sourceFile, inModi = _a.inModi, indexes = _a.indexes, toModi = _a.toModi, actionType = _a.actionType, _static = _a.static, hasRelationDef = _a.hasRelationDef; var fromSchemaSpecifiers = [ factory.createImportSpecifier(false, undefined, factory.createIdentifier("OpSchema")) ]; - if (relationHierarchy) { - fromSchemaSpecifiers.push(factory.createImportSpecifier(false, undefined, factory.createIdentifier("Relation"))); - } + /* if (relationHierarchy || reverseCascadeRelationHierarchy) { + fromSchemaSpecifiers.push( + factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier("Relation") + ) + ); + } */ var statements = [ factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("StorageDesc"))])), factory.createStringLiteral("".concat((0, env_1.TYPE_PATH_IN_OAK_DOMAIN)(), "Storage")), undefined), factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports(fromSchemaSpecifiers)), factory.createStringLiteral("./Schema"), undefined) @@ -3110,15 +3138,43 @@ function outputStorage(outputDir, printer) { if (indexExpressions.length > 0) { propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("indexes"), factory.createArrayLiteralExpression(indexExpressions, true))); } - if (relationHierarchy) { - propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("relationHierarchy"), relationHierarchy)); + /* if (relationHierarchy) { + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("relationHierarchy"), + relationHierarchy, + ) + ); + } + if (reverseCascadeRelationHierarchy) { + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("reverseCascadeRelationHierarchy"), + reverseCascadeRelationHierarchy, + ) + ); + } */ + if (hasRelationDef) { + var type = hasRelationDef.type; + if (ts.isUnionTypeNode(type)) { + var types = type.types; + var relationTexts = types.map(function (ele) { + (0, assert_1.default)(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal)); + return ele.literal.text; + }); + propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("relation"), factory.createArrayLiteralExpression(relationTexts.map(function (ele) { return factory.createStringLiteral(ele); })))); + } + else { + (0, assert_1.default)(ts.isLiteralTypeNode(type)); + (0, assert_1.default)(ts.isStringLiteral(type.literal)); + propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("relation"), factory.createArrayLiteralExpression([ + type.literal + ]))); + } } var sdTypeArguments = [ factory.createTypeReferenceNode(factory.createIdentifier("OpSchema"), undefined) ]; - if (relationHierarchy) { - sdTypeArguments.push(factory.createTypeReferenceNode(factory.createIdentifier("Relation"), undefined)); - } statements.push(factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("desc"), undefined, factory.createTypeReferenceNode(factory.createIdentifier("StorageDesc"), sdTypeArguments), factory.createObjectLiteralExpression(propertyAssignments, true))], ts.NodeFlags.Const))); var result_2 = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements), sourceFile); var filename_1 = path_1.default.join(outputDir, entity, 'Storage.ts'); diff --git a/lib/store/CascadeStore.d.ts b/lib/store/CascadeStore.d.ts index 844d93b..c8346eb 100644 --- a/lib/store/CascadeStore.d.ts +++ b/lib/store/CascadeStore.d.ts @@ -1,4 +1,4 @@ -import { EntityDict, OperateOption, SelectOption, OperationResult, DeduceFilter, AggregationResult } from "../types/Entity"; +import { EntityDict, OperateOption, SelectOption, OperationResult, AggregationResult } from "../types/Entity"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { RowStore } from '../types/RowStore'; import { StorageSchema } from '../types/Storage'; @@ -51,14 +51,14 @@ export declare abstract class CascadeStore | AsyncContext, OP extends OperateOption, R>(entity: T, action: ED[T]['Action'], data: ED[T]['CreateSingle']['data'] | ED[T]['Update']['data'] | ED[T]['Remove']['data'], context: Cxt, option: OP, cascadeUpdate: (entity: T2, operation: ED[T2]['Operation'], context: Cxt, option: OP) => R, filter?: DeduceFilter): { + protected destructCascadeUpdate | AsyncContext, OP extends OperateOption, R>(entity: T, action: ED[T]['Action'], data: ED[T]['CreateSingle']['data'] | ED[T]['Update']['data'] | ED[T]['Remove']['data'], context: Cxt, option: OP, cascadeUpdate: (entity: T2, operation: ED[T2]['Operation'], context: Cxt, option: OP) => R, filter?: ED[T]['Update']['filter']): { data: Record; beforeFns: (() => R)[]; afterFns: (() => R)[]; }; protected preProcessDataCreated(entity: T, data: ED[T]['Create']['data']): void; - protected preProcessDataUpdated(data: ED[T]['Update']['data']): void; - judgeRelation(entity: keyof ED, attr: string): string | 2 | 1 | string[] | 0; + protected preProcessDataUpdated(data: Record): void; + judgeRelation(entity: keyof ED, attr: string): string | 1 | 2 | string[] | 0; /** * 和具体的update过程无关的例程放在这里,包括对later动作的处理、对oper的记录以及对record的收集等 * @param entity diff --git a/lib/store/TriggerExecutor.js b/lib/store/TriggerExecutor.js index 5971219..0e51be6 100644 --- a/lib/store/TriggerExecutor.js +++ b/lib/store/TriggerExecutor.js @@ -32,15 +32,15 @@ var TriggerExecutor = /** @class */ (function () { TriggerExecutor.prototype.registerChecker = function (checker) { var entity = checker.entity, action = checker.action, type = checker.type, conditionalFilter = checker.conditionalFilter; var triggerName = "".concat(String(entity)).concat(action, "\u6743\u9650\u68C0\u67E5-").concat(this.counter++); - var fn = (0, checker_1.translateCheckerInAsyncContext)(checker); + var _a = (0, checker_1.translateCheckerInAsyncContext)(checker), fn = _a.fn, when = _a.when; var trigger = { checkerType: type, name: triggerName, - priority: checker.priority || 2, + priority: checker.priority || 20, entity: entity, action: action, fn: fn, - when: 'before', + when: when, filter: conditionalFilter, }; this.registerTrigger(trigger); @@ -59,7 +59,7 @@ var TriggerExecutor = /** @class */ (function () { throw new Error("\u4E0D\u53EF\u6709\u540C\u540D\u7684\u89E6\u53D1\u5668\u300C".concat(trigger.name, "\u300D")); } if (typeof trigger.priority !== 'number') { - trigger.priority = 1; // 默认最低 + trigger.priority = 10; // 默认值 } if (trigger.filter) { (0, assert_1.default)(typeof trigger.action === 'string' && trigger.action !== 'create' @@ -205,6 +205,7 @@ var TriggerExecutor = /** @class */ (function () { (0, assert_1.default)(operation.action !== 'create'); var filter = trigger.filter; var filterr = typeof filter === 'function' ? filter(operation, context, option) : filter; + (0, assert_1.default)(!(filterr instanceof Promise)); var filterRepelled = (0, filter_1.checkFilterRepel)(entity, context, filterr, operation.filter); if (filterRepelled) { continue; @@ -228,28 +229,37 @@ var TriggerExecutor = /** @class */ (function () { else { // 异步context var execPreTrigger_1 = function (idx) { return tslib_1.__awaiter(_this, void 0, void 0, function () { - var trigger, filter, filterr, filterRepelled, number; - return tslib_1.__generator(this, function (_a) { - switch (_a.label) { + var trigger, filter, filterr, _a, filterRepelled, number; + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { case 0: if (idx >= preTriggers_2.length) { return [2 /*return*/]; } trigger = preTriggers_2[idx]; - if (!trigger.filter) return [3 /*break*/, 2]; + if (!trigger.filter) return [3 /*break*/, 5]; (0, assert_1.default)(operation.action !== 'create'); filter = trigger.filter; - filterr = typeof filter === 'function' ? filter(operation, context, option) : filter; - return [4 /*yield*/, (0, filter_1.checkFilterRepel)(entity, context, filterr, operation.filter)]; + if (!(typeof filter === 'function')) return [3 /*break*/, 2]; + return [4 /*yield*/, filter(operation, context, option)]; case 1: - filterRepelled = _a.sent(); + _a = _b.sent(); + return [3 /*break*/, 3]; + case 2: + _a = filter; + _b.label = 3; + case 3: + filterr = _a; + return [4 /*yield*/, (0, filter_1.checkFilterRepel)(entity, context, filterr, operation.filter)]; + case 4: + filterRepelled = _b.sent(); if (filterRepelled) { return [2 /*return*/, execPreTrigger_1(idx + 1)]; } - _a.label = 2; - case 2: return [4 /*yield*/, trigger.fn({ operation: operation }, context, option)]; - case 3: - number = _a.sent(); + _b.label = 5; + case 5: return [4 /*yield*/, trigger.fn({ operation: operation }, context, option)]; + case 6: + number = _b.sent(); if (number > 0) { this.logger.info("\u89E6\u53D1\u5668\u300C".concat(trigger.name, "\u300D\u6210\u529F\u89E6\u53D1\u4E86\u300C").concat(number, "\u300D\u884C\u6570\u636E\u66F4\u6539")); } @@ -258,28 +268,37 @@ var TriggerExecutor = /** @class */ (function () { }); }); }; var execCommitTrigger_1 = function (idx) { return tslib_1.__awaiter(_this, void 0, void 0, function () { - var trigger, filter, filterr, filterRepelled; - return tslib_1.__generator(this, function (_a) { - switch (_a.label) { + var trigger, filter, filterr, _a, filterRepelled; + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { case 0: if (idx >= commitTriggers_1.length) { return [2 /*return*/]; } trigger = commitTriggers_1[idx]; - if (!trigger.filter) return [3 /*break*/, 2]; + if (!trigger.filter) return [3 /*break*/, 5]; (0, assert_1.default)(operation.action !== 'create'); filter = trigger.filter; - filterr = typeof filter === 'function' ? filter(operation, context, option) : filter; - return [4 /*yield*/, (0, filter_1.checkFilterRepel)(entity, context, filterr, operation.filter)]; + if (!(typeof filter === 'function')) return [3 /*break*/, 2]; + return [4 /*yield*/, filter(operation, context, option)]; case 1: - filterRepelled = _a.sent(); + _a = _b.sent(); + return [3 /*break*/, 3]; + case 2: + _a = filter; + _b.label = 3; + case 3: + filterr = _a; + return [4 /*yield*/, (0, filter_1.checkFilterRepel)(entity, context, filterr, operation.filter)]; + case 4: + filterRepelled = _b.sent(); if (filterRepelled) { return [2 /*return*/, execCommitTrigger_1(idx + 1)]; } - _a.label = 2; - case 2: return [4 /*yield*/, this.preCommitTrigger(entity, operation, trigger, context, option)]; - case 3: - _a.sent(); + _b.label = 5; + case 5: return [4 /*yield*/, this.preCommitTrigger(entity, operation, trigger, context, option)]; + case 6: + _b.sent(); return [2 /*return*/, execCommitTrigger_1(idx + 1)]; } }); diff --git a/lib/store/checker.d.ts b/lib/store/checker.d.ts index 4ad1052..533c708 100644 --- a/lib/store/checker.d.ts +++ b/lib/store/checker.d.ts @@ -1,7 +1,13 @@ -import { Checker, EntityDict, OperateOption, SelectOption, StorageSchema, Trigger } from "../types"; +import { AuthDefDict, Checker, EntityDict, OperateOption, SelectOption, StorageSchema, Trigger } from "../types"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { AsyncContext } from "./AsyncRowStore"; import { SyncContext } from './SyncRowStore'; -export declare function translateCheckerInAsyncContext>(checker: Checker): Trigger['fn']; -export declare function translateCheckerInSyncContext>(checker: Checker): (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void; -export declare function createRelationHierarchyCheckers | SyncContext>(schema: StorageSchema): Checker[]; +export declare function translateCheckerInAsyncContext>(checker: Checker): { + fn: Trigger['fn']; + when: 'before' | 'after'; +}; +export declare function translateCheckerInSyncContext>(checker: Checker): { + fn: (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void; + when: 'before' | 'after'; +}; +export declare function createAuthCheckers | SyncContext>(schema: StorageSchema, authDict: AuthDefDict): Checker[]; diff --git a/lib/store/checker.js b/lib/store/checker.js index 27c3aff..e3a35d8 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -1,58 +1,79 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.createRelationHierarchyCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0; +exports.createAuthCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0; var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var filter_1 = require("../store/filter"); var Exception_1 = require("../types/Exception"); var actionDef_1 = require("./actionDef"); var string_1 = require("../utils/string"); +var lodash_1 = require("../utils/lodash"); +var relation_1 = require("./relation"); function translateCheckerInAsyncContext(checker) { var _this = this; - var entity = checker.entity, type = checker.type; + 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'; switch (type) { case 'data': { var checkerFn_1 = checker.checker; - return (function (_a, context) { + var fn = (function (_a, context) { var operation = _a.operation; return tslib_1.__awaiter(_this, void 0, void 0, function () { var data; return tslib_1.__generator(this, function (_b) { - data = operation.data; - checkerFn_1(data, context); - return [2 /*return*/, 0]; + switch (_b.label) { + case 0: + data = operation.data; + return [4 /*yield*/, checkerFn_1(data, context)]; + case 1: + _b.sent(); + return [2 /*return*/, 0]; + } }); }); }); + return { + fn: fn, + when: when, + }; } case 'row': { var filter_2 = checker.filter, errMsg_1 = checker.errMsg, inconsistentRows_1 = checker.inconsistentRows; - return (function (_a, context, option) { + var fn = (function (_a, context, option) { var operation = _a.operation; return tslib_1.__awaiter(_this, void 0, void 0, function () { - var operationFilter, action, filter2, entity2, selection2, rows2, data_1, rows2, data_2; - var _b, _c; - return tslib_1.__generator(this, function (_d) { - switch (_d.label) { + var operationFilter, action, filter2, _b, entity2, selection2, rows2, data_1, rows2, data_2; + var _c, _d; + return tslib_1.__generator(this, function (_e) { + switch (_e.label) { case 0: operationFilter = operation.filter, action = operation.action; - filter2 = typeof filter_2 === 'function' ? filter_2(operation, context, option) : filter_2; - if (!['select', 'count', 'stat'].includes(action)) return [3 /*break*/, 1]; + if (!(typeof filter_2 === 'function')) return [3 /*break*/, 2]; + return [4 /*yield*/, filter_2(operation, context, option)]; + case 1: + _b = _e.sent(); + return [3 /*break*/, 3]; + case 2: + _b = filter_2; + _e.label = 3; + case 3: + filter2 = _b; + if (!['select', 'count', 'stat'].includes(action)) return [3 /*break*/, 4]; operation.filter = (0, filter_1.addFilterSegment)(operationFilter || {}, filter2); return [2 /*return*/, 0]; - case 1: return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter || {})]; - case 2: - if (_d.sent()) { + case 4: return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter || {}, true)]; + case 5: + if (_e.sent()) { return [2 /*return*/, 0]; } - if (!inconsistentRows_1) return [3 /*break*/, 4]; + if (!inconsistentRows_1) return [3 /*break*/, 7]; entity2 = inconsistentRows_1.entity, selection2 = inconsistentRows_1.selection; return [4 /*yield*/, context.select(entity2, selection2(operationFilter), { dontCollect: true, blockTrigger: true, })]; - case 3: - rows2 = _d.sent(); + case 6: + rows2 = _e.sent(); data_1 = {}; rows2.forEach(function (ele) { var _a; @@ -62,11 +83,11 @@ function translateCheckerInAsyncContext(checker) { }); throw new Exception_1.OakRowInconsistencyException({ a: 's', - d: (_b = {}, - _b[entity2] = data_1, - _b) + d: (_c = {}, + _c[entity2] = data_1, + _c) }, errMsg_1); - case 4: return [4 /*yield*/, context.select(entity, { + case 7: return [4 /*yield*/, context.select(entity, { data: (0, actionDef_1.getFullProjection)(entity, context.getSchema()), filter: Object.assign({}, operationFilter, { $not: filter2, @@ -75,8 +96,8 @@ function translateCheckerInAsyncContext(checker) { dontCollect: true, blockTrigger: true, })]; - case 5: - rows2 = _d.sent(); + case 8: + rows2 = _e.sent(); data_2 = {}; rows2.forEach(function (ele) { var _a; @@ -86,70 +107,91 @@ function translateCheckerInAsyncContext(checker) { }); throw new Exception_1.OakRowInconsistencyException({ a: 's', - d: (_c = {}, - _c[entity] = data_2, - _c) + d: (_d = {}, + _d[entity] = data_2, + _d) }, errMsg_1); } }); }); }); + return { + fn: fn, + when: when, + }; } case 'relation': { - var relationFilter_1 = checker.relationFilter; - return (function (_a, context, option) { + var relationFilter_1 = checker.relationFilter, errMsg_2 = 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) { + case 0: + if (context.isRoot()) { + return [2 /*return*/, 0]; + } + if (!(operation.action === 'create')) return [3 /*break*/, 3]; + 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)]; + case 2: + if (_e.sent()) { + return [2 /*return*/, 0]; + } + throw new Exception_1.OakUserUnpermittedException(errMsg_2); + 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]; + } + }); + }); + }); + return { + fn: fn, + when: when, + }; + } + case 'logical': + case 'logicalRelation': { + var checkerFn_2 = checker.checker; + var fn = (function (_a, context, option) { var operation = _a.operation; return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_b) { - if (context.isRoot()) { - return [2 /*return*/, 0]; - } - // 对后台而言,将生成的relationFilter加到filter之上(select可以在此加以权限的过滤) - operation.filter = (0, filter_1.combineFilters)([operation.filter, relationFilter_1(operation, context, option)]); - return [2 /*return*/, 0]; - }); - }); - }); - } - case 'expression': - case 'expressionRelation': { - var expression_1 = checker.expression, errMsg_2 = checker.errMsg; - return (function (_a, context, option) { - var operation = _a.operation; - return tslib_1.__awaiter(_this, void 0, void 0, function () { - var exprResult, expressionEntity, expr, expressionFilter, _b, result; - return tslib_1.__generator(this, function (_c) { - switch (_c.label) { + switch (_b.label) { case 0: - if (context.isRoot() && type === 'expressionRelation') { + if (context.isRoot() && type === 'logicalRelation') { return [2 /*return*/, 0]; } - exprResult = expression_1(operation, context, option); - if (!(typeof exprResult === 'string')) return [3 /*break*/, 1]; - throw new Exception_1.OakUserUnpermittedException(exprResult || errMsg_2); + return [4 /*yield*/, checkerFn_2(operation, context, option)]; case 1: - if (!(exprResult === undefined)) return [3 /*break*/, 2]; + _b.sent(); return [2 /*return*/, 0]; - case 2: - expressionEntity = exprResult.entity, expr = exprResult.expr, expressionFilter = exprResult.filter; - return [4 /*yield*/, context.select(expressionEntity, { - data: { - $expr: expr, - }, - filter: expressionFilter, - }, Object.assign({}, option, { dontCollect: true }))]; - case 3: - _b = tslib_1.__read.apply(void 0, [_c.sent(), 1]), result = _b[0]; - if (!result) { - // 条件判定为假,抛异常 - throw new Exception_1.OakUserUnpermittedException(errMsg_2); - } - _c.label = 4; - case 4: return [2 /*return*/, 0]; } }); }); }); + return { + fn: fn, + when: when, + }; } default: { (0, assert_1.default)(false); @@ -158,15 +200,20 @@ function translateCheckerInAsyncContext(checker) { } exports.translateCheckerInAsyncContext = translateCheckerInAsyncContext; function translateCheckerInSyncContext(checker) { - var entity = checker.entity, type = checker.type; + 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'; switch (type) { case 'data': { - var checkerFn_2 = checker.checker; - return function (operation, context) { return checkerFn_2(operation.data, context); }; + var checkerFn_3 = checker.checker; + var fn = function (operation, context) { return checkerFn_3(operation.data, context); }; + return { + fn: fn, + when: when, + }; } case 'row': { var filter_3 = checker.filter, errMsg_3 = checker.errMsg; - return function (operation, context, option) { + 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; (0, assert_1.default)(operationFilter); @@ -175,56 +222,59 @@ function translateCheckerInSyncContext(checker) { return 0; } else { - if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter)) { + (0, assert_1.default)(!(filter2 instanceof Promise)); + if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter, true)) { return; } throw new Exception_1.OakRowInconsistencyException(undefined, errMsg_3); } }; + return { + fn: fn, + when: when, + }; } case 'relation': { - var filter_4 = checker.relationFilter, errMsg_4 = checker.errMsg; - return function (operation, context, option) { + var relationFilter_2 = checker.relationFilter, errMsg_4 = checker.errMsg; + var fn = function (operation, context, option) { if (context.isRoot()) { return; } - var filter2 = typeof filter_4 === 'function' ? filter_4(operation, context, option) : filter_4; - var operationFilter = operation.filter; - (0, assert_1.default)(operationFilter); - if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter)) { + 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 }; + } + (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, + when: when, + }; } - case 'expression': - case 'expressionRelation': { - var expression_2 = checker.expression, errMsg_5 = checker.errMsg; - return function (operation, context, option) { - if (context.isRoot() && type === 'expressionRelation') { - return; - } - var exprResult = expression_2(operation, context, option); - if (typeof exprResult === 'string') { - throw new Exception_1.OakUserUnpermittedException(exprResult || errMsg_5); - } - else if (exprResult === undefined) { - return 0; - } - else { - var expressionEntity = exprResult.entity, expr = exprResult.expr, expressionFilter = exprResult.filter; - var _a = tslib_1.__read(context.select(expressionEntity, { - data: { - $expr: expr, - }, - filter: expressionFilter, - }, Object.assign({}, option, { dontCollect: true })), 1), result = _a[0]; - if (!result.$expr) { - // 条件判定为假,抛异常 - throw new Exception_1.OakRowInconsistencyException(undefined, errMsg_5); - } + case 'logical': + case 'logicalRelation': { + var checkerFn_4 = checker.checker; + var fn = function (operation, context, option) { + if (context.isRoot() && type === 'logicalRelation') { return; } + checkerFn_4(operation, context, option); + }; + return { + fn: fn, + when: when, }; } default: { @@ -233,154 +283,211 @@ function translateCheckerInSyncContext(checker) { } } exports.translateCheckerInSyncContext = translateCheckerInSyncContext; -function createRelationHierarchyCheckers(schema) { +function translateCascadeRelationFilterMaker(schema, lch, entity2) { + var cascadePath = lch.cascadePath, relations = lch.relations; + var paths = cascadePath.split('.'); + var translateRelationFilter = function (entity) { + // 有两种情况,此entity和user有Relation定义,或是此entity上有userId + if (schema[entity].relation) { + var relationEntityName_1 = "user".concat((0, string_1.firstLetterUpperCase)(entity)); + return function (userId) { + var _a; + var filter = relations ? { + userId: userId, + relation: { + $in: relations, + }, + } : { + userId: userId, + }; + return { + id: { + $in: { + entity: relationEntityName_1, + data: (_a = {}, + _a["".concat(entity, "Id")] = 1, + _a), + filter: filter, + }, + }, + }; + }; + } + var attributes = schema[entity].attributes; + (0, assert_1.default)(attributes.hasOwnProperty('userId') && attributes.userId.type === 'ref' && attributes.userId.ref === 'user', "\u5728".concat(entity, "\u4E0A\u65E2\u627E\u4E0D\u5230userId\uFF0C\u4E5F\u6CA1\u6709relation\u5B9A\u4E49")); + return function (userId) { return ({ + userId: userId, + }); }; + }; + var translateFilterMakerIter = function (entity, iter) { + var relation = (0, relation_1.judgeRelation)(schema, entity, paths[iter]); + if (iter === paths.length - 1) { + if (relation === 2) { + var filterMaker_1 = translateRelationFilter(paths[iter]); + return function (userId) { + var _a; + var filter = filterMaker_1(userId); + if (filter.$in) { + return { + entity: paths[iter], + entityId: filter, + }; + } + return _a = {}, + _a[paths[iter]] = filter, + _a; + }; + } + (0, assert_1.default)(typeof relation === 'string'); + var filterMaker_2 = translateRelationFilter(relation); + return function (userId) { + var _a, _b; + var filter = filterMaker_2(userId); + if (filter.$in) { + return _a = {}, + _a["".concat(paths[iter], "Id")] = filter, + _a; + } + return _b = {}, + _b[paths[iter]] = filter, + _b; + }; + } + 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; + }; + } + return function (userId) { + var _a; + return (_a = {}, + _a[paths[iter]] = subFilterMaker_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 = translateCascadeRelationFilterMaker(schema, relationItem, entity); + return function (userId) { return filterMaker(userId); }; +} +function createAuthCheckers(schema, authDict) { var checkers = []; var _loop_1 = function (entity) { - var e_1, _a; - var relationHierarchy = schema[entity].relationHierarchy; - if (relationHierarchy) { - // 先build反向hierarchy的map - var reverseHierarchy_1 = {}; - for (var r in relationHierarchy) { - try { - for (var _b = (e_1 = void 0, tslib_1.__values(relationHierarchy[r])), _c = _b.next(); !_c.done; _c = _b.next()) { - var r2 = _c.value; - if (!reverseHierarchy_1[r2]) { - reverseHierarchy_1[r2] = [r]; - } - else { - reverseHierarchy_1[r2].push(r); - } - } + var _a; + if (authDict[entity]) { + var _b = authDict[entity], relationAuth = _b.relationAuth, actionAuth = _b.actionAuth; + if (relationAuth) { + var raFilterMakerDict_1 = {}; + for (var r in relationAuth) { + Object.assign(raFilterMakerDict_1, (_a = {}, + _a[r] = translateActionAuthFilterMaker(schema, relationAuth[r], entity), + _a)); } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (_c && !_c.done && (_a = _b.return)) _a.call(_b); - } - finally { if (e_1) throw e_1.error; } - } - } - // 对userEntity对象的授权和回收建立checker - var userEntityName_1 = "user".concat((0, string_1.firstLetterUpperCase)(entity)); - var entityIdAttr_1 = "".concat(entity, "Id"); - checkers.push({ - entity: userEntityName_1, - action: 'create', - type: 'expressionRelation', - expression: function (operation, context) { - var _a; - var data = operation.data; - var _b = data, relation = _b.relation, _c = entityIdAttr_1, entityId = _b[_c]; - var legalRelations = reverseHierarchy_1[relation]; - if (!legalRelations) { - return undefined; - } - if (legalRelations.length === 0) { - return '这是不应该跑出来的情况,请杀程序员祭天'; - } - var userId = context.getCurrentUserId(); - return { - entity: userEntityName_1, - expr: { - $gt: [{ - '#attr': '$$createAt$$', - }, 0] - }, - filter: (_a = { - userId: userId - }, - _a[entityIdAttr_1] = entityId, - _a.relation = { - $in: legalRelations, - }, - _a) - }; - }, - errMsg: '越权操作', - }); - var _loop_2 = function (r) { + var userEntityName_1 = "user".concat((0, string_1.firstLetterUpperCase)(entity)); + var entityIdAttr_1 = "".concat(entity, "Id"); checkers.push({ entity: userEntityName_1, - action: 'remove', - type: 'expressionRelation', - conditionalFilter: { - relation: r, - }, - expression: function (operation, context) { - var _a, _b; + 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 filter = operation.filter; - var legalRelations = reverseHierarchy_1[r]; - if (legalRelations.length === 0) { - return '这是不应该跑出来的情况,请杀程序员祭天'; + if (!raFilterMakerDict_1[relation]) { + return; } - return { - entity: userEntityName_1, - expr: { - $gt: [{ - '#attr': '$$createAt$$', - }, 0] - }, - filter: (_a = { - userId: userId - }, - _a[entityIdAttr_1] = { - $in: { - entity: userEntityName_1, - data: (_b = {}, - _b[entityIdAttr_1] = 1, - _b), - filter: filter, - } - }, - _a.relation = { - $in: legalRelations, - }, - _a), - }; + var filter = raFilterMakerDict_1[relation](userId); + return _a = {}, + _a[entity] = filter, + _a; }, errMsg: '越权操作', }); - }; - for (var r in reverseHierarchy_1) { - _loop_2(r); - } - /* // 一个人不能授权给自己,也不能删除自己的授权 - checkers.push({ - entity: userEntityName as keyof ED, - action: 'create' as ED[keyof ED]['Action'], - type: 'data', - checker: (data, context) => { - assert(!(data instanceof Array)); - const { userId } = data as ED[keyof ED]['CreateSingle']['data']; - const userId2 = context.getCurrentUserId(true); - if (userId === userId2) { - throw new OakDataException('不允许授权给自己'); - } - } - }); - - checkers.push({ - entity: userEntityName as keyof ED, - action: 'remove' as ED[keyof ED]['Action'], - type: 'row', - filter: (operation, context) => { - const userId = context.getCurrentUserId(true); - if (userId) { - return { - userId: { - $ne: userId, - }, + checkers.push({ + entity: userEntityName_1, + 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")); + // 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); + }) + }; }; - } - console.warn(`没有当前用户但在删除权限,请检查。对象是${entity}`); - return {}; - }, - errMsg: '不允许回收自己的授权', - }); */ - // 转让权限现在用update动作,只允许update userId给其它人 - // todo 等实现的时候再写 + var toBeRemoved = context.select(userEntityName_1, { + data: (_a = { + id: 1, + relation: 1 + }, + _a[entityIdAttr_1] = 1, + _a), + filter: filter, + }, { dontCollect: true }); + if (toBeRemoved instanceof Promise) { + return toBeRemoved.then(function (rows) { return makeFilterFromRows(rows); }); + } + return makeFilterFromRows(toBeRemoved); + }, + errMsg: '越权操作', + }); + // 转让权限现在用update动作,只允许update userId给其它人 + // todo 等实现的时候再写 + } + if (actionAuth) { + var _loop_2 = function (a) { + var filterMaker = translateActionAuthFilterMaker(schema, actionAuth[a], entity); + checkers.push({ + entity: entity, + action: a, + type: 'relation', + relationFilter: function (operation, context) { + // const { filter } = operation; + var filter = filterMaker(context.getCurrentUserId()); + return filter; + }, + errMsg: '定义的actionAuth中检查出来越权操作', + }); + }; + for (var a in actionAuth) { + _loop_2(a); + } + } } }; for (var entity in schema) { @@ -388,4 +495,4 @@ function createRelationHierarchyCheckers(schema) { } return checkers; } -exports.createRelationHierarchyCheckers = createRelationHierarchyCheckers; +exports.createAuthCheckers = createAuthCheckers; diff --git a/lib/store/filter.d.ts b/lib/store/filter.d.ts index 364cf90..6f7c213 100644 --- a/lib/store/filter.d.ts +++ b/lib/store/filter.d.ts @@ -96,5 +96,14 @@ export declare function makeTreeAncestorFilter(entity: T, parentKey: string, filter: ED[T]['Selection']['filter'], level?: number, includeAll?: boolean, includeSelf?: boolean): ED[T]['Selection']['filter']; -export declare function checkFilterContains | AsyncContext>(entity: T, context: Cxt, contained: ED[T]['Selection']['filter'], filter?: ED[T]['Selection']['filter']): boolean | Promise; -export declare function checkFilterRepel | AsyncContext>(entity: T, context: Cxt, filter1: ED[T]['Selection']['filter'], filter2: ED[T]['Selection']['filter']): boolean | Promise; +/** + * 检查filter是否包含contained(filter查询的数据一定满足contained) + * @param entity + * @param context + * @param contained + * @param filter + * @param dataCompare + * @returns + */ +export declare function checkFilterContains | AsyncContext>(entity: T, context: Cxt, contained: ED[T]['Selection']['filter'], filter?: ED[T]['Selection']['filter'], dataCompare?: true): boolean | Promise; +export declare function checkFilterRepel | AsyncContext>(entity: T, context: Cxt, filter1: ED[T]['Selection']['filter'], filter2: ED[T]['Selection']['filter'], dataCompare?: true): boolean | Promise; diff --git a/lib/store/filter.js b/lib/store/filter.js index 4d2edfd..17099ed 100644 --- a/lib/store/filter.js +++ b/lib/store/filter.js @@ -828,7 +828,16 @@ function makeTreeDescendantFilter(entity, parentKey, filter, level, includeAll, return currentLevelInFilter; } exports.makeTreeDescendantFilter = makeTreeDescendantFilter; -function checkFilterContains(entity, context, contained, filter) { +/** + * 检查filter是否包含contained(filter查询的数据一定满足contained) + * @param entity + * @param context + * @param contained + * @param filter + * @param dataCompare + * @returns + */ +function checkFilterContains(entity, context, contained, filter, dataCompare) { if (!filter) { throw new types_1.OakRowInconsistencyException(); } @@ -837,23 +846,26 @@ function checkFilterContains(entity, context, contained, filter) { if (contains(entity, schema, filter, contained)) { return true; } - // 再判断加上了conditionalFilter后取得的行数是否缩减 - var filter2 = combineFilters([filter, { - $not: contained, - }]); - var count = context.count(entity, { - filter: filter2, - }, { - dontCollect: true, - blockTrigger: true, - }); - if (count instanceof Promise) { - return count.then(function (count2) { return count2 === 0; }); + if (dataCompare) { + // 再判断加上了conditionalFilter后取得的行数是否缩减 + var filter2 = combineFilters([filter, { + $not: contained, + }]); + var count = context.count(entity, { + filter: filter2, + }, { + dontCollect: true, + blockTrigger: true, + }); + if (count instanceof Promise) { + return count.then(function (count2) { return count2 === 0; }); + } + return count === 0; } - return count === 0; + return false; } exports.checkFilterContains = checkFilterContains; -function checkFilterRepel(entity, context, filter1, filter2) { +function checkFilterRepel(entity, context, filter1, filter2, dataCompare) { if (!filter2) { throw new types_1.OakRowInconsistencyException(); } @@ -863,16 +875,19 @@ function checkFilterRepel(entity, context, filter1, filter2) { return true; } // 再判断两者同时成立时取得的行数是否为0 - var filter3 = combineFilters([filter2, filter1]); - var count = context.count(entity, { - filter: filter3, - }, { - dontCollect: true, - blockTrigger: true, - }); - if (count instanceof Promise) { - return count.then(function (count2) { return count2 === 0; }); + if (dataCompare) { + var filter3 = combineFilters([filter2, filter1]); + var count = context.count(entity, { + filter: filter3, + }, { + dontCollect: true, + blockTrigger: true, + }); + if (count instanceof Promise) { + return count.then(function (count2) { return count2 === 0; }); + } + return count === 0; } - return count === 0; + return false; } exports.checkFilterRepel = checkFilterRepel; diff --git a/lib/store/modi.js b/lib/store/modi.js index 4dc3d91..3cc1f0a 100644 --- a/lib/store/modi.js +++ b/lib/store/modi.js @@ -58,14 +58,6 @@ function abandonModis(filter, context, option) { _d.action = 'abandon', _d.data = {}, _d.filter = filter, - _d.sorter = [ - { - $attr: { - $$createAt$$: 1, - }, - $direction: 'asc', - } - ], _d), Object.assign({}, option, { blockTrigger: false, })]))]; diff --git a/lib/store/relation.d.ts b/lib/store/relation.d.ts index 9def51f..4c9e882 100644 --- a/lib/store/relation.d.ts +++ b/lib/store/relation.d.ts @@ -10,4 +10,4 @@ import { StorageSchema } from "../types/Storage"; */ export declare function judgeRelation(schema: StorageSchema, entity: keyof ED, attr: string): string | 2 | 1 | string[] | 0; +}>(schema: StorageSchema, entity: keyof ED, attr: string): string | 1 | 2 | string[] | 0; diff --git a/lib/store/relation.js b/lib/store/relation.js index bb01df9..9338ae4 100644 --- a/lib/store/relation.js +++ b/lib/store/relation.js @@ -59,7 +59,7 @@ function judgeRelation(schema, entity, attr) { return 2; } else { - (0, assert_1.default)(Entity_1.initinctiveAttributes.includes(attr), "".concat(attr, "\u5C5E\u6027\u627E\u4E0D\u5230")); + (0, assert_1.default)(Entity_1.initinctiveAttributes.includes(attr), "".concat(entity, "\u5BF9\u8C61\u4E2D\u7684").concat(attr, "\u5C5E\u6027\u627E\u4E0D\u5230")); return 1; } } diff --git a/lib/types/Action.d.ts b/lib/types/Action.d.ts index efdf041..242e274 100644 --- a/lib/types/Action.d.ts +++ b/lib/types/Action.d.ts @@ -1,4 +1,5 @@ -import { EntityDict } from "./Entity"; +import { CascadeRelationItem, EntityDict } from "./Entity"; +import { GenericAction } from '../actions/action'; export declare type Action = string; export declare type State = string; export declare type ActionDef = { @@ -12,3 +13,6 @@ export declare type ActionDictOfEntityDict = { [A in keyof E[T]['OpSchema']]?: ActionDef; }; }; +export declare type CascadeActionAuth = { + [K in A | GenericAction]?: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[]; +}; diff --git a/lib/types/Auth.d.ts b/lib/types/Auth.d.ts index 28a9536..cee2d7b 100644 --- a/lib/types/Auth.d.ts +++ b/lib/types/Auth.d.ts @@ -1,8 +1,8 @@ +import { CascadeActionAuth, CascadeRelationAuth } from "."; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption, SelectOption } from "../types/Entity"; -import { RefOrExpression } from "./Expression"; -export declare type CheckerType = 'relation' | 'row' | 'data' | 'expression' | 'expressionRelation'; +export declare type CheckerType = 'relation' | 'row' | 'data' | 'logical' | 'logicalRelation'; /** * conditionalFilter是指该action发生时,operation所操作的行中有满足conditionalFilter的行 * 被转化成trigger的filter条件,详细可看trigger中的说明 @@ -12,55 +12,55 @@ export declare type DataChecker | Array>; - checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => void; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => void | Promise; + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); }; 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']); + filter: ED[T]['Selection']['filter'] | ((operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise); 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']); + conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); }; export declare type RelationChecker | SyncContext> = { priority?: number; type: 'relation'; entity: T; - action: Omit | Array>; - relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter']; + when?: 'after'; + action: ED[T]['Action'] | Array; + relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise; errMsg: string; - 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) => ED[T]['Update']['filter'] | Promise); }; -export declare type ExpressionChecker | SyncContext> = { +export declare type LogicalChecker | SyncContext> = { priority?: number; - type: 'expression'; + type: 'logical'; + when?: 'after'; entity: T; action: ED[T]['Action'] | Array; - expression: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => { - entity: T2; - expr: RefOrExpression; - filter: ED[T2]['Selection']['filter']; - } | undefined | string; - errMsg: string; + checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => void | Promise; conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); }; -export declare type ExpressionRelationChecker | SyncContext> = { +export declare type LogicalRelationChecker | SyncContext> = { priority?: number; - type: 'expressionRelation'; + type: 'logicalRelation'; + when?: 'after'; entity: T; action: ED[T]['Action'] | Array; - expression: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => { - entity: T2; - expr: RefOrExpression; - filter: ED[T2]['Selection']['filter']; - } | undefined | string; - errMsg: string; + checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => void | Promise; conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); }; -export declare type Checker | SyncContext> = DataChecker | RowChecker | RelationChecker | ExpressionChecker | ExpressionRelationChecker; +export declare type Checker | SyncContext> = DataChecker | RowChecker | RelationChecker | LogicalChecker | LogicalRelationChecker; +export declare type AuthDef = { + relationAuth?: CascadeRelationAuth>; + actionAuth?: CascadeActionAuth; +}; +export declare type AuthDefDict = { + [K in keyof ED]?: AuthDef; +}; diff --git a/lib/types/Endpoint.d.ts b/lib/types/Endpoint.d.ts new file mode 100644 index 0000000..df3f727 --- /dev/null +++ b/lib/types/Endpoint.d.ts @@ -0,0 +1,10 @@ +/// +import { IncomingHttpHeaders, IncomingMessage } from "http"; +import { AsyncContext } from "../store/AsyncRowStore"; +import { EntityDict } from "./Entity"; +export interface Endpoint> { + name: string; + params?: string[]; + method: 'get' | 'post' | 'put' | 'delete'; + fn: (context: BackCxt, params: Record, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise; +} diff --git a/lib/types/Endpoint.js b/lib/types/Endpoint.js new file mode 100644 index 0000000..04363ad --- /dev/null +++ b/lib/types/Endpoint.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +; diff --git a/lib/types/Entity.d.ts b/lib/types/Entity.d.ts index b49b463..84060eb 100644 --- a/lib/types/Entity.d.ts +++ b/lib/types/Entity.d.ts @@ -1,6 +1,3 @@ -import { GenericAction } from '../actions/action'; -import { ExprOp, MakeFilter, NodeId } from './Demand'; -import { OneOf } from './Polyfill'; import { PrimaryKey, Sequence } from './DataType'; declare type TriggerDataAttributeType = '$$triggerData$$'; declare type TriggerTimestampAttributeType = '$$triggerTimestamp$$'; @@ -18,7 +15,7 @@ export declare const DeleteAtAttribute = "$$deleteAt$$"; export declare const SeqAttribute = "$$seq$$"; export declare type InstinctiveAttributes = PrimaryKeyAttributeType | CreateAtAttributeType | UpdateAtAttributeType | DeleteAtAttributeType | TriggerDataAttributeType | TriggerTimestampAttributeType | SeqAttributeType; export declare const initinctiveAttributes: string[]; -export declare type Filter = { +declare type FilterPart = { filter?: A extends 'create' ? undefined : F; indexFrom?: A extends 'create' ? undefined : number; count?: A extends 'create' ? undefined : number; @@ -44,16 +41,15 @@ export declare type OperateOption = { export declare type FormUpdateData = Partial<{ [K in keyof Omit]: SH[K] | null; }>; -export declare type FormCreateData = Omit & { +export declare type FormCreateData = Partial> & { id: string; }; -export declare type Operation = { +export declare type Operation = { id?: string; action: A; - data: DATA; - sorter?: SORTER; -} & Filter; -export declare type Selection = Operation<'select', DATA, FILTER, SORT>; + data: D; + sorter?: S; +} & FilterPart; export interface EntityShape { id: PrimaryKey; $$seq$$: Sequence; @@ -61,8 +57,6 @@ export interface EntityShape { $$updateAt$$: number | Date; $$deleteAt$$?: number | Date | null; } -export interface FileCarrierEntityShape extends EntityShape { -} interface GeneralEntityShape extends EntityShape { [K: string]: any; } @@ -72,28 +66,24 @@ export interface EntityDef { OpSchema: GeneralEntityShape; Action: string; ParticularAction?: string; - Selection: Omit, 'action'>; - Aggregation: Omit, DeduceFilter, DeduceSorter>, 'action'>; - Operation: DeduceOperation; - Create: DeduceCreateOperation; - CreateSingle: DeduceCreateSingleOperation; - CreateMulti: DeduceCreateMultipleOperation; - Update: DeduceUpdateOperation; - Remove: DeduceRemoveOperation; + Selection: Omit, 'action'>; + Aggregation: Omit, 'action'>; + Operation: CUDOperation; + Create: CreateOperation; + CreateSingle: CreateSingleOperation; + CreateMulti: CreateMultipleOperation; + Update: UpdateOperation; + Remove: RemoveOperation; + Relation?: string; } export interface EntityDict { [E: string]: EntityDef; } -export interface OtmSubProjection extends Omit, 'action'> { +export interface OtmSubProjection extends Omit, 'action'> { $entity: string; } -declare type DeduceProjection = { - '#id'?: NodeId; -} & { - [K in keyof SH]?: number | OtmSubProjection | any; -} & Partial>; export declare type AggregationOp = `#max-${number}` | `#min-${number}` | `#avg-${number}` | `#count-${number}` | `#sum-${number}`; -export declare type DeduceAggregationData> = { +export declare type DeduceAggregationData

= { [A in AggregationOp]?: P; } & { '#aggr'?: P; @@ -104,36 +94,40 @@ export declare type AggregationResult = Array<{ '#data'?: Partial; }>; export declare type AttrFilter = { - [K in keyof SH]: any; + [K in keyof SH]?: any; }; -export declare type DeduceFilter = MakeFilter & ExprOp>; -export declare type DeduceSorterAttr = OneOf<{ - [K: string]: number | object | undefined; -} & ExprOp>; -export declare type DeduceSorterItem = { - $attr: DeduceSorterAttr; +declare type SortAttr = { + [K: string]: any; +}; +declare type SorterItem = { + $attr: SortAttr; $direction?: "asc" | "desc"; }; -export declare type DeduceSorter = Array>; -export declare type DeduceSelection = Selection, DeduceFilter, DeduceSorter>; -export declare type DeduceAggregation, F extends DeduceFilter, S extends DeduceSorter> = Omit, F, S>, 'action'>; -export declare type DeduceCreateOperationData = { +declare type Sorter = Array; +declare type Filter = { + [K: string]: any; +}; +declare type Projection = { + [K: string]: any; +}; +export declare type DeduceAggregation

= Omit, F, S>, 'action'>; +declare type CreateOperationData = { id: string; -} & { - [k in keyof Omit]?: any; + [K: string]: any; }; -export declare type DeduceCreateSingleOperation = Operation<'create', DeduceCreateOperationData>; -export declare type DeduceCreateMultipleOperation = Operation<'create', Array>>; -export declare type DeduceCreateOperation = DeduceCreateSingleOperation | DeduceCreateMultipleOperation; -export declare type DeduceUpdateOperationData = { - [k in keyof Omit]?: any; +declare type CreateSingleOperation = Operation<'create', CreateOperationData, undefined, undefined>; +declare type CreateMultipleOperation = Operation<'create', Array, undefined, undefined>; +declare type CreateOperation = CreateSingleOperation | CreateMultipleOperation; +declare type UpdateOperationData = { + id?: never; + [k: string]: any; }; -export declare type DeduceUpdateOperation = Operation<'update' | string, DeduceUpdateOperationData, DeduceFilter, DeduceSorter>; -export declare type DeduceRemoveOperationData = { - [A in keyof Omit]?: any; +export declare type UpdateOperation = Operation; +declare type RemoveOperationData = { + [k: string]: any; }; -export declare type DeduceRemoveOperation = Operation<'remove', DeduceRemoveOperationData, DeduceFilter, DeduceSorter>; -export declare type DeduceOperation = DeduceCreateOperation | DeduceUpdateOperation | DeduceRemoveOperation; +export declare type RemoveOperation = Operation<'remove', RemoveOperationData, Filter, Sorter>; +export declare type CUDOperation = CreateOperation | UpdateOperation | RemoveOperation; export declare type CreateOpResult = { a: 'c'; e: T; @@ -142,17 +136,24 @@ export declare type CreateOpResult = export declare type UpdateOpResult = { a: 'u'; e: T; - d: DeduceUpdateOperationData; - f?: DeduceFilter; + d: UpdateOperationData; + f?: Filter; }; export declare type RemoveOpResult = { a: 'r'; e: T; - f?: DeduceFilter; + f?: Filter; }; export declare type RelationHierarchy = { [K in R]?: R[]; }; +export declare type CascadeRelationItem = { + cascadePath: string; + relations?: string[]; +}; +export declare type CascadeRelationAuth = { + [K in R]?: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[]; +}; export declare type SelectOpResult = { a: 's'; d: { @@ -172,19 +173,4 @@ export declare type Configuration = { actionType?: ActionType; static?: boolean; }; -export declare type Exportation = { - name: string; - id: string; - entity: T; - projection: ED[T]['Selection']['data']; - headers: K[]; - fn: (data: ED[T]['Schema']) => Partial>; -}; -export declare type Importation = { - name: string; - id: string; - entity: T; - headers: K[]; - fn: (data: Partial>) => ED[T]['CreateSingle']['data']; -}; export {}; diff --git a/lib/types/Entity.js b/lib/types/Entity.js index b9c71bb..20f99ba 100644 --- a/lib/types/Entity.js +++ b/lib/types/Entity.js @@ -12,4 +12,3 @@ exports.initinctiveAttributes = [exports.PrimaryKeyAttribute, exports.TriggerDat ; ; ; -; diff --git a/lib/types/Exception.d.ts b/lib/types/Exception.d.ts index 67eeb5e..13bdcb9 100644 --- a/lib/types/Exception.d.ts +++ b/lib/types/Exception.d.ts @@ -5,6 +5,11 @@ export declare class OakException extends Error { } export declare class OakDataException extends OakException { } +export declare class OakImportDataParseException extends OakException { + line: number; + header?: string; + constructor(message: string, line: number, header?: string); +} export declare class OakOperExistedException extends OakDataException { } export declare class OakRowUnexistedException extends OakDataException { diff --git a/lib/types/Exception.js b/lib/types/Exception.js index 6cceffc..33d3e27 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.OakDataException = exports.OakException = void 0; +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.OakDataException = exports.OakException = void 0; var tslib_1 = require("tslib"); var OakException = /** @class */ (function (_super) { tslib_1.__extends(OakException, _super); @@ -36,6 +36,18 @@ var OakDataException = /** @class */ (function (_super) { return OakDataException; }(OakException)); exports.OakDataException = OakDataException; +var OakImportDataParseException = /** @class */ (function (_super) { + tslib_1.__extends(OakImportDataParseException, _super); + // message必传,描述具体错误的数据内容 + function OakImportDataParseException(message, line, header) { + var _this = _super.call(this, message) || this; + _this.line = line; + _this.header = header; + return _this; + } + return OakImportDataParseException; +}(OakException)); +exports.OakImportDataParseException = OakImportDataParseException; var OakOperExistedException = /** @class */ (function (_super) { tslib_1.__extends(OakOperExistedException, _super); function OakOperExistedException() { @@ -245,6 +257,9 @@ function makeException(data) { case 'OakDeadlock': { return new OakDeadlock(data.message); } + case 'OakImportDataParseException': { + return new OakImportDataParseException(data.message, data.line, data.header); + } default: return; } diff --git a/lib/types/Expression.d.ts b/lib/types/Expression.d.ts index a76c16f..cdeb126 100644 --- a/lib/types/Expression.d.ts +++ b/lib/types/Expression.d.ts @@ -4,7 +4,7 @@ export declare type RefOrExpression = RefAttr | Expression; declare type MathType = RefOrExpression | number; declare type StringType = RefOrExpression | string; interface Add { - $add: (MathType | StringType)[]; + $add: (MathType)[]; } interface Subtract { $subtract: [MathType, MathType]; @@ -111,6 +111,10 @@ interface DateFloor { $dateFloor: [RefOrExpression | Date | number, 'y' | 'M' | 'd' | 'h' | 'm' | 's']; } declare type DateExpression = DateYear | DateMonth | DateWeekday | DateWeekOfYear | DateDay | DateDayOfYear | DateDayOfMonth | DateDayOfWeek | DateDiff | DateCeiling | DateFloor; +interface StringConcat { + $concat: StringType[]; +} +declare type StringExpression = StringConcat; interface GeoContains { $contains: [RefOrExpression | Geo, RefOrExpression | Geo]; } @@ -118,7 +122,23 @@ interface GeoDistance { $distance: [RefOrExpression | Geo, RefOrExpression | Geo]; } declare type GeoExpression = GeoContains | GeoDistance; -export declare type Expression = GeoExpression | DateExpression | LogicExpression | BoolExpression | CompareExpression | MathExpression; +interface AggrCountExpression { + $$count: RefOrExpression; +} +interface AggrSumExpression { + $$sum: RefOrExpression; +} +interface AggrMaxExpression { + $$max: RefOrExpression; +} +interface AggrMinExpression { + $$min: RefOrExpression; +} +interface AggrAvgExpression { + $$avg: RefOrExpression; +} +export declare type AggrExpression = AggrAvgExpression | AggrCountExpression | AggrSumExpression | AggrMaxExpression | AggrMinExpression; +export declare type Expression = GeoExpression | DateExpression | LogicExpression | BoolExpression | CompareExpression | MathExpression | StringExpression | AggrExpression; export declare type ExpressionConstant = Geo | number | Date | string | boolean; export declare function isGeoExpression(expression: any): expression is GeoExpression; export declare function isDateExpression(expression: any): expression is DateExpression; @@ -126,6 +146,8 @@ export declare function isLogicExpression(expression: any): expression is Log export declare function isBoolExpression(expression: any): expression is BoolExpression; export declare function isCompareExpression(expression: any): expression is CompareExpression; export declare function isMathExpression(expression: any): expression is MathExpression; +export declare function isStringExpression(expression: any): expression is StringExpression; +export declare function isAggrExpression(expression: any): expression is AggrExpression; export declare function isExpression(expression: any): expression is Expression; export declare function opMultipleParams(op: string): boolean; export declare function execOp(op: string, params: any, obscure?: boolean): ExpressionConstant; diff --git a/lib/types/Expression.js b/lib/types/Expression.js index ade7d9f..64c0471 100644 --- a/lib/types/Expression.js +++ b/lib/types/Expression.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getAttrRefInExpression = exports.execOp = exports.opMultipleParams = exports.isExpression = exports.isMathExpression = exports.isCompareExpression = exports.isBoolExpression = exports.isLogicExpression = exports.isDateExpression = exports.isGeoExpression = void 0; +exports.getAttrRefInExpression = exports.execOp = exports.opMultipleParams = exports.isExpression = exports.isAggrExpression = exports.isStringExpression = exports.isMathExpression = exports.isCompareExpression = exports.isBoolExpression = exports.isLogicExpression = exports.isDateExpression = exports.isGeoExpression = void 0; var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var dayjs_1 = tslib_1.__importDefault(require("dayjs")); @@ -42,6 +42,7 @@ dayjs_1.default.extend(dayOfYear_1.default); ; ; ; +; function isGeoExpression(expression) { if (Object.keys(expression).length == 1) { var op = Object.keys(expression)[0]; @@ -105,13 +106,34 @@ function isMathExpression(expression) { return false; } exports.isMathExpression = isMathExpression; +function isStringExpression(expression) { + if (Object.keys(expression).length == 1) { + var op = Object.keys(expression)[0]; + if (['$concat'].includes(op)) { + return true; + } + } + return false; +} +exports.isStringExpression = isStringExpression; +function isAggrExpression(expression) { + if (Object.keys(expression).length == 1) { + var op = Object.keys(expression)[0]; + if (['$$max', '$$min', '$$sum', '$$avg', '$$count'].includes(op)) { + return true; + } + } + return false; +} +exports.isAggrExpression = isAggrExpression; function isExpression(expression) { return typeof expression === 'object' && Object.keys(expression).length === 1 && Object.keys(expression)[0].startsWith('$'); } exports.isExpression = isExpression; function opMultipleParams(op) { return !['$year', '$month', '$weekday', '$weekOfYear', '$day', '$dayOfMonth', - '$dayOfWeek', '$dayOfYear', '$not', '$true', '$false', '$abs', '$round', '$floor', '$ceil'].includes(op); + '$dayOfWeek', '$dayOfYear', '$not', '$true', '$false', '$abs', + '$round', '$floor', '$ceil', '$$max', '$$min', '$$sum', '$$avg', '$$count'].includes(op); } exports.opMultipleParams = opMultipleParams; function execOp(op, params, obscure) { @@ -341,6 +363,9 @@ function execOp(op, params, obscure) { case '$contains': { throw new Error('$contains类型未实现'); } + case '$concat': { + return params.join(''); + } default: { (0, assert_1.default)(false, "\u4E0D\u80FD\u8BC6\u522B\u7684expression\u8FD0\u7B97\u7B26\uFF1A".concat(op)); } diff --git a/lib/types/Port.d.ts b/lib/types/Port.d.ts new file mode 100644 index 0000000..8e0519a --- /dev/null +++ b/lib/types/Port.d.ts @@ -0,0 +1,17 @@ +import { AsyncContext } from "../store/AsyncRowStore"; +import { EntityDict } from "./Entity"; +export declare type Exportation = { + name: string; + id: string; + entity: T; + projection: ED[T]['Selection']['data']; + headers: K[]; + fn: (data: ED[T]['Schema']) => Partial>; +}; +export declare type Importation = { + name: string; + id: string; + entity: T; + headers: K[]; + fn: (data: Partial>[], context: AsyncContext, option?: Record) => Promise; +}; diff --git a/lib/types/Port.js b/lib/types/Port.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/lib/types/Port.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/types/Storage.d.ts b/lib/types/Storage.d.ts index e082f77..341fabc 100644 --- a/lib/types/Storage.d.ts +++ b/lib/types/Storage.d.ts @@ -1,5 +1,5 @@ import { ActionType } from '.'; -import { EntityDict, EntityShape, InstinctiveAttributes, RelationHierarchy } from './Entity'; +import { EntityDict, EntityShape, InstinctiveAttributes } from './Entity'; import { DataType, DataTypeParams } from './schema/DataTypes'; export declare type Ref = 'ref'; export interface Column { @@ -36,7 +36,7 @@ export declare type UniqConstraint = { attributes: Array; type?: string; }; -export interface StorageDesc { +export interface StorageDesc { storageName?: string; comment?: string; attributes: Attributes; @@ -48,9 +48,9 @@ export interface StorageDesc; + [K in keyof ED]: StorageDesc; }; diff --git a/lib/types/Trigger.d.ts b/lib/types/Trigger.d.ts index 128e85c..0015048 100644 --- a/lib/types/Trigger.d.ts +++ b/lib/types/Trigger.d.ts @@ -37,7 +37,7 @@ export interface UpdateTriggerBase Promise | number; - filter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Update'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + filter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Update'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); } export interface UpdateTriggerInTxn | SyncContext> extends UpdateTriggerBase { when: 'before' | 'after'; @@ -58,7 +58,7 @@ export interface RemoveTriggerBase Promise | number; - filter?: ED[T]['Remove']['filter'] | ((operation: ED[T]['Remove'], context: Cxt, option: OperateOption) => ED[T]['Remove']['filter']); + filter?: ED[T]['Remove']['filter'] | ((operation: ED[T]['Remove'], context: Cxt, option: OperateOption) => ED[T]['Remove']['filter'] | Promise); } export interface RemoveTriggerInTxn | SyncContext> extends RemoveTriggerBase { when: 'before' | 'after'; diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index bace749..dcb0909 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -17,3 +17,5 @@ export * from './Watcher'; export * from './AppLoader'; export * from './Connector'; export * from './Timer'; +export * from './Port'; +export * from './Endpoint'; diff --git a/lib/types/index.js b/lib/types/index.js index 12853e8..986fd63 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -20,3 +20,5 @@ tslib_1.__exportStar(require("./Watcher"), exports); tslib_1.__exportStar(require("./AppLoader"), exports); tslib_1.__exportStar(require("./Connector"), exports); tslib_1.__exportStar(require("./Timer"), exports); +tslib_1.__exportStar(require("./Port"), exports); +tslib_1.__exportStar(require("./Endpoint"), exports); diff --git a/lib/utils/date.d.ts b/lib/utils/date.d.ts new file mode 100644 index 0000000..dfb8c6f --- /dev/null +++ b/lib/utils/date.d.ts @@ -0,0 +1 @@ +export declare function excelStringToDate(str: string | number): number | undefined; diff --git a/lib/utils/date.js b/lib/utils/date.js new file mode 100644 index 0000000..5b6dc09 --- /dev/null +++ b/lib/utils/date.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.excelStringToDate = void 0; +var tslib_1 = require("tslib"); +var dayjs_1 = tslib_1.__importDefault(require("dayjs")); +function excelStringToDate(str) { + if (!str) { + return undefined; + } + if (typeof str === 'number') { + if (str < 100000) { + return (0, dayjs_1.default)((((str - 25569) * 24 - 8) * 3600) * 1000).valueOf(); // excel日期可能为1900-1-1至今的天数 + } + return (0, dayjs_1.default)(str).valueOf(); + } + return Date.parse(str); +} +exports.excelStringToDate = excelStringToDate; diff --git a/package.json b/package.json index ec44077..21e882f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.3.2", + "version": "2.4.0", "author": { "name": "XuChang" }, @@ -37,7 +37,7 @@ "mocha": "^8.2.1", "ts-node": "~10.9.1", "tslib": "^2.4.0", - "typescript": "~4.7.4" + "typescript": "^4.9.4" }, "dependencies": { "@datasert/cronjs-matcher": "^1.2.0", diff --git a/src/actions/relation.ts b/src/actions/relation.ts index d59cadc..d958bd7 100644 --- a/src/actions/relation.ts +++ b/src/actions/relation.ts @@ -1 +1,30 @@ -export type GenericRelation = 'owner'; \ No newline at end of file +import { CascadeRelationItem, RelationHierarchy } from "../types/Entity"; + +export type GenericRelation = 'owner'; + +export function convertHierarchyToAuth(hierarchy: RelationHierarchy): { + [K in R]?: CascadeRelationItem; +} { + const reverseHierarchy: RelationHierarchy = {}; + for (const r in hierarchy) { + for (const r2 of hierarchy[r]!) { + if (reverseHierarchy[r2]) { + reverseHierarchy[r2]?.push(r); + } + else { + reverseHierarchy[r2] = [r]; + } + } + } + const result: { + [K in R]?: CascadeRelationItem; + } = {}; + for (const r in reverseHierarchy) { + result[r] = { + cascadePath: '', + relations: reverseHierarchy[r], + }; + } + + return result; +} \ No newline at end of file diff --git a/src/checkers/index.ts b/src/checkers/index.ts index 3cb2927..55f462d 100644 --- a/src/checkers/index.ts +++ b/src/checkers/index.ts @@ -1,13 +1,15 @@ import { EntityDict } from '../base-app-domain'; import { AsyncContext } from '../store/AsyncRowStore'; -import { createRelationHierarchyCheckers } from '../store/checker'; +import { createAuthCheckers } from '../store/checker'; import { createModiRelatedCheckers } from '../store/modi'; import { SyncContext } from '../store/SyncRowStore'; -import { StorageSchema, EntityDict as BaseEntityDict, Checker } from '../types'; +import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDef, AuthDefDict } from '../types'; -export function createDynamicCheckers | SyncContext>(schema: StorageSchema){ +export function createDynamicCheckers | SyncContext>(schema: StorageSchema, authDict?: AuthDefDict){ const checkers: Checker[] = []; checkers.push(...createModiRelatedCheckers(schema)); - checkers.push(...createRelationHierarchyCheckers(schema)); + if (authDict) { + checkers.push(...createAuthCheckers(schema, authDict)); + } return checkers; } diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index 4e79ee5..4d4f572 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -25,7 +25,8 @@ const Schema: Record { if (ts.isImportDeclaration(node)) { const entityImported = getEntityImported(node); @@ -1123,15 +1125,22 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela _static = true; // static如果有值只能为true } } - else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'RelationHierarchy') { + /* else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'RelationHierarchy') { // RelationHierary assert(hasRelationDef, `${moduleName}中的Relation定义在RelationHierarchy之后`); const { initializer } = declaration; assert(ts.isObjectLiteralExpression(initializer!), `${moduleName}中的RelationHierarchy的定义必须是初始化为ObjectLiteralExpress`); relationHierarchy = initializer; } + else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ReverseCascadeRelationHierarchy') { + // ReverseCascadeRelationHierarchy + assert(hasRelationDef, `${moduleName}中的Relation定义在ReverseCascadeRelationHierarchy之后`); + const { initializer } = declaration; + assert(ts.isObjectLiteralExpression(initializer!), `${moduleName}中的RelationHierarchy的定义必须是初始化为ObjectLiteralExpress`); + reverseCascadeRelationHierarchy = initializer; + } */ else { - throw new Error(`${moduleName}:不能理解的定义内容${declaration.name.getText()}`); + throw new Error(`${moduleName}:不能理解的定义内容${(declaration.name as ts.Identifier).text}`); } } ); @@ -1173,15 +1182,25 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela locale: localeDef, }); } - if (hasRelationDef) { - assert(relationHierarchy, `${filename}中缺少了relationHierarchy定义`); - assign(schema, { - relationHierarchy, - }); + /* if (hasRelationDef) { + if(!relationHierarchy && !reverseCascadeRelationHierarchy){ + console.warn(`${filename}中定义了Relation,但并没有relationHierarchy或reverseCascadeRelationHierarchy的定义,请注意自主编写权限分配的checker`); + } + if (relationHierarchy) { + assign(schema, { + relationHierarchy, + }); + } + if (reverseCascadeRelationHierarchy) { + assign(schema, { + reverseCascadeRelationHierarchy, + }); + } } else { assert(!relationHierarchy, `${filename}中具有relationHierarchy定义但没有Relation定义`); - } + assert(!reverseCascadeRelationHierarchy, `${filename}中具有reverseCascadeRelationHierarchy定义但没有Relation定义`) + } */ assign(Schema, { [moduleName]: schema, @@ -2864,10 +2883,6 @@ function constructActions(statements: Array, entity: string) { factory.createTypeReferenceNode( factory.createIdentifier("DeduceAggregation"), [ - factory.createTypeReferenceNode( - factory.createIdentifier("Schema"), - undefined - ), factory.createTypeReferenceNode( factory.createIdentifier("Projection"), undefined @@ -3078,6 +3093,13 @@ function constructActions(statements: Array, entity: string) { const reverseOneNodes: ts.TypeNode[] = []; if (ReversePointerEntities[entity]) { if (ReversePointerRelations[entity]) { + const { schemaAttrs } = Schema[entity]; + const { questionToken } = schemaAttrs.find( + ele => { + const { name } = ele; + return (name).text === 'entity' + } + )!; for (const one of ReversePointerRelations[entity]) { const cascadeCreateNode = factory.createTypeLiteralNode( [ @@ -3136,14 +3158,14 @@ function constructActions(statements: Array, entity: string) { factory.createPropertySignature( undefined, factory.createIdentifier('entity'), - undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc + questionToken, factory.createLiteralTypeNode(factory.createStringLiteral(`${firstLetterLowerCase(one)}`) ) ), factory.createPropertySignature( undefined, factory.createIdentifier('entityId'), - undefined, // 反向指针好像不能为空,以后或许会有特例 by Xc + questionToken, factory.createTypeReferenceNode( factory.createIdentifier("String"), [factory.createLiteralTypeNode(factory.createNumericLiteral("64"))] @@ -4292,10 +4314,6 @@ function constructActions(statements: Array, entity: string) { factory.createTypeReferenceNode( factory.createIdentifier("RemoveOperation"), undefined - ), - factory.createTypeReferenceNode( - factory.createIdentifier("SelectOperation"), - undefined ) ]) ) @@ -4536,23 +4554,6 @@ const initialStatements = () => [ function outputSubQuery(outputDir: string, printer: ts.Printer) { const statements: ts.Statement[] = []; if (process.env.COMPLING_AS_LIB) { - statements.push( - factory.createImportDeclaration( - undefined, - undefined, - factory.createImportClause( - false, - undefined, - factory.createNamedImports([factory.createImportSpecifier( - false, - undefined, - factory.createIdentifier("Selection") - )]) - ), - factory.createStringLiteral(TYPE_PATH_IN_OAK_DOMAIN(1)), - undefined - ) - ); } for (const entity in Schema) { // import * as User from '../User/Schema'; @@ -4903,7 +4904,8 @@ function outputSchema(outputDir: string, printer: ts.Printer) { constructSorter(statements, entity); constructActions(statements, entity); constructQuery(statements, entity); - constructFullAttrs(statements, entity); + // 现在FullAttrs和NativeAttrs似乎没什么用,还会引起递归 + // constructFullAttrs(statements, entity); const makeActionArguments: ts.TypeNode[] = []; if (ActionAsts[entity]) { @@ -5042,6 +5044,19 @@ function outputSchema(outputDir: string, printer: ts.Printer) { ) ); } + if (typeof Schema[entity].hasRelationDef === 'object' && ts.isTypeAliasDeclaration(Schema[entity].hasRelationDef as ts.Node)) { + EntityDefAttrs.push( + factory.createPropertySignature( + undefined, + factory.createIdentifier("Relation"), + undefined, + factory.createTypeReferenceNode( + factory.createIdentifier('Relation'), + undefined + ) + ) + ); + } statements.push( factory.createTypeAliasDeclaration( undefined, @@ -5638,7 +5653,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) { for (const entity in Schema) { const indexExpressions: ts.Expression[] = []; - const { sourceFile, inModi, indexes, toModi, actionType, static: _static, relationHierarchy } = Schema[entity]; + const { sourceFile, inModi, indexes, toModi, actionType, static: _static, hasRelationDef } = Schema[entity]; const fromSchemaSpecifiers = [ factory.createImportSpecifier( false, @@ -5646,7 +5661,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) { factory.createIdentifier("OpSchema") ) ]; - if (relationHierarchy) { + /* if (relationHierarchy || reverseCascadeRelationHierarchy) { fromSchemaSpecifiers.push( factory.createImportSpecifier( false, @@ -5654,7 +5669,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) { factory.createIdentifier("Relation") ) ); - } + } */ const statements: ts.Statement[] = [ factory.createImportDeclaration( undefined, @@ -5855,7 +5870,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) { ) ); } - if (relationHierarchy) { + /* if (relationHierarchy) { propertyAssignments.push( factory.createPropertyAssignment( factory.createIdentifier("relationHierarchy"), @@ -5863,20 +5878,55 @@ function outputStorage(outputDir: string, printer: ts.Printer) { ) ); } + if (reverseCascadeRelationHierarchy) { + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("reverseCascadeRelationHierarchy"), + reverseCascadeRelationHierarchy, + ) + ); + } */ + if (hasRelationDef) { + const { type } = hasRelationDef; + if (ts.isUnionTypeNode(type)) { + const { types } = type; + const relationTexts = types.map( + ele => { + assert(ts.isLiteralTypeNode(ele) && ts.isStringLiteral(ele.literal)); + return ele.literal.text; + } + ) + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("relation"), + factory.createArrayLiteralExpression(relationTexts.map( + ele => factory.createStringLiteral(ele) + )), + ) + ); + } + else { + assert(ts.isLiteralTypeNode(type)); + assert(ts.isStringLiteral(type.literal)); + + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("relation"), + factory.createArrayLiteralExpression( + [ + type.literal + ] + ), + ) + ); + } + } const sdTypeArguments = [ factory.createTypeReferenceNode( factory.createIdentifier("OpSchema"), undefined ) ]; - if (relationHierarchy) { - sdTypeArguments.push( - factory.createTypeReferenceNode( - factory.createIdentifier("Relation"), - undefined - ) - ) - } statements.push( factory.createVariableStatement( [factory.createModifier(ts.SyntaxKind.ExportKeyword)], diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index 632747a..52a74cc 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -1,7 +1,7 @@ import assert from "assert"; import { EntityDict, - OperateOption, SelectOption, OperationResult, DeduceFilter, CreateAtAttribute, UpdateAtAttribute, AggregationResult, DeleteAtAttribute + OperateOption, SelectOption, OperationResult, CreateAtAttribute, UpdateAtAttribute, AggregationResult, DeleteAtAttribute } from "../types/Entity"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { RowStore } from '../types/RowStore'; @@ -525,7 +525,7 @@ export abstract class CascadeStore exten operation: ED[T2]['Operation'], context: Cxt, option: OP) => R, - filter?: DeduceFilter + filter?: ED[T]['Update']['filter'] ) { const modiAttr = this.getSchema()[entity].toModi; const option2 = Object.assign({}, option); @@ -830,7 +830,7 @@ export abstract class CascadeStore exten } // 对更新的数据,去掉所有的undefined属性 - protected preProcessDataUpdated(data: ED[T]['Update']['data']) { + protected preProcessDataUpdated(data: Record) { const undefinedKeys = Object.keys(data).filter( ele => data[ele] === undefined ); @@ -852,7 +852,7 @@ export abstract class CascadeStore exten * @param option */ private async doUpdateSingleRowAsync>(entity: T, - operation: ED[T]['Operation'], + operation: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], context: Cxt, option: OP ) { @@ -1182,7 +1182,7 @@ export abstract class CascadeStore exten id: { $in: ids, } - } as DeduceFilter, + }, }); } } @@ -1202,7 +1202,7 @@ export abstract class CascadeStore exten id: { $in: ids, } - } as DeduceFilter, + }, }); } } diff --git a/src/store/TriggerExecutor.ts b/src/store/TriggerExecutor.ts index 9024c9b..a250a8f 100644 --- a/src/store/TriggerExecutor.ts +++ b/src/store/TriggerExecutor.ts @@ -1,10 +1,10 @@ import assert from 'assert'; import { pull, unset } from "../utils/lodash"; import { addFilterSegment, checkFilterRepel } from "../store/filter"; -import { DeduceCreateOperation, EntityDict, OperateOption, SelectOption, TriggerDataAttribute, TriggerTimestampAttribute } from "../types/Entity"; +import { EntityDict, OperateOption, SelectOption, TriggerDataAttribute, TriggerTimestampAttribute } from "../types/Entity"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { Logger } from "../types/Logger"; -import { Checker, CheckerType } from '../types/Auth'; +import { Checker, CheckerType, LogicalChecker, RelationChecker } from '../types/Auth'; import { Trigger, CreateTriggerCrossTxn, CreateTrigger, CreateTriggerInTxn, SelectTriggerAfter, UpdateTrigger } from "../types/Trigger"; import { AsyncContext } from './AsyncRowStore'; import { SyncContext } from './SyncRowStore'; @@ -49,15 +49,15 @@ export class TriggerExecutor { registerChecker>(checker: Checker): void { const { entity, action, type, conditionalFilter } = checker; const triggerName = `${String(entity)}${action}权限检查-${this.counter++}`; - const fn = translateCheckerInAsyncContext(checker); + const { fn, when } = translateCheckerInAsyncContext(checker); const trigger = { checkerType: type, name: triggerName, - priority: checker.priority || 2, // checker的默认优先级稍高一点点 + priority: checker.priority || 20, // checker的默认优先级稍高 entity, action: action as 'update', fn, - when: 'before', + when, filter: conditionalFilter, } as UpdateTrigger; this.registerTrigger(trigger); @@ -77,7 +77,7 @@ export class TriggerExecutor { throw new Error(`不可有同名的触发器「${trigger.name}」`); } if (typeof trigger.priority !== 'number') { - trigger.priority = 1; // 默认最低 + trigger.priority = 10; // 默认值 } if ((trigger as UpdateTrigger).filter) { assert(typeof trigger.action === 'string' && trigger.action !== 'create' @@ -229,8 +229,9 @@ export class TriggerExecutor { // trigger只对满足条件的前项进行判断,如果确定不满足可以pass assert(operation.action !== 'create'); const { filter } = trigger as UpdateTrigger; - const filterr = typeof filter === 'function' ? filter(operation, context, option) : filter; - const filterRepelled = checkFilterRepel(entity, context, filterr, operation.filter) as boolean + const filterr = typeof filter === 'function' ? filter(operation as ED[T]['Update'], context, option) : filter; + assert(!(filterr instanceof Promise)); + const filterRepelled = checkFilterRepel(entity, context, filterr, operation.filter) as boolean if (filterRepelled) { continue; } @@ -252,8 +253,8 @@ export class TriggerExecutor { if ((trigger as UpdateTrigger).filter) { assert(operation.action !== 'create'); const { filter } = trigger as UpdateTrigger; - const filterr = typeof filter === 'function' ? filter(operation, context, option) : filter; - const filterRepelled = await (checkFilterRepel(entity, context, filterr, operation.filter) as Promise); + const filterr = typeof filter === 'function' ? await filter(operation as ED[T]['Update'], context, option) : filter; + const filterRepelled = await (checkFilterRepel(entity, context, filterr, operation.filter) as Promise); if (filterRepelled) { return execPreTrigger(idx + 1); } @@ -272,8 +273,8 @@ export class TriggerExecutor { if ((trigger as UpdateTrigger).filter) { assert(operation.action !== 'create'); const { filter } = trigger as UpdateTrigger; - const filterr = typeof filter === 'function' ? filter(operation, context, option) : filter; - const filterRepelled = await (checkFilterRepel(entity, context, filterr, operation.filter) as Promise); + const filterr = typeof filter === 'function' ? await filter(operation as ED[T]['Update'], context, option) : filter; + const filterRepelled = await (checkFilterRepel(entity, context, filterr, operation.filter) as Promise); if (filterRepelled) { return execCommitTrigger(idx + 1); } @@ -294,7 +295,7 @@ export class TriggerExecutor { return async () => { await context.begin(); const number = await (trigger as CreateTrigger>).fn({ - operation: operation as DeduceCreateOperation, + operation: operation as ED[T]['Create'], }, context, option); if ((trigger as CreateTriggerCrossTxn).strict === 'makeSure') { // 如果是必须完成的trigger,在完成成功后要把trigger相关的属性置null; @@ -354,10 +355,10 @@ export class TriggerExecutor { ); if (triggers) { const postTriggers = triggers.filter( - ele => ele.when === 'after' && (!(ele as CreateTrigger).check || (ele as CreateTrigger).check!(operation as DeduceCreateOperation)) + ele => ele.when === 'after' && (!(ele as CreateTrigger).check || (ele as CreateTrigger).check!(operation as ED[T]['Create'])) ); const commitTriggers = (>>triggers).filter( - ele => ele.when === 'commit' && (!ele.check || ele.check(operation as DeduceCreateOperation)) + ele => ele.when === 'commit' && (!ele.check || ele.check(operation as ED[T]['Create'])) ); if (context instanceof SyncContext) { diff --git a/src/store/checker.ts b/src/store/checker.ts index 3853c54..ae421d1 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -1,38 +1,51 @@ import assert from 'assert'; import { addFilterSegment, checkFilterContains, combineFilters } from "../store/filter"; -import { OakDataException, OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; -import { Checker, CreateTriggerInTxn, EntityDict, ExpressionRelationChecker, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn } from "../types"; +import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; +import { + AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn, + EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy +} from "../types"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { AsyncContext } from "./AsyncRowStore"; import { getFullProjection } from './actionDef'; import { SyncContext } from './SyncRowStore'; import { firstLetterUpperCase } from '../utils/string'; +import { uniq } from '../utils/lodash'; +import { judgeRelation } from './relation'; export function translateCheckerInAsyncContext< ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext ->(checker: Checker): Trigger['fn'] { - const { entity, type } = checker; +>(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' switch (type) { case 'data': { const { checker: checkerFn } = checker; - return (async ({ operation }, context) => { + const fn = (async ({ operation }, context) => { const { data } = operation; - checkerFn(data, context); + await checkerFn(data, context); return 0; }) as CreateTriggerInTxn['fn']; + return { + fn, + when, + }; } case 'row': { const { filter, errMsg, inconsistentRows } = checker; - return (async ({ operation }, context, option) => { + const fn = (async ({ operation }, context, option) => { const { filter: operationFilter, action } = operation; - const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter; + const filter2 = typeof filter === 'function' ? await (filter as Function)(operation, context, option) : filter; if (['select', 'count', 'stat'].includes(action)) { operation.filter = addFilterSegment(operationFilter || {}, filter2); return 0; } else { - if (await checkFilterContains(entity, context, filter2, operationFilter || {})) { + if (await checkFilterContains(entity, context, filter2, operationFilter || {}, true)) { return 0; } if (inconsistentRows) { @@ -81,47 +94,61 @@ export function translateCheckerInAsyncContext< } } }) as UpdateTriggerInTxn['fn']; + return { + fn, + when, + }; } case 'relation': { - const { relationFilter } = checker; - return (async ({ operation }, context, option) => { + const { relationFilter, errMsg } = checker; + const fn = (async ({ operation }, context, option) => { if (context.isRoot()) { return 0; } + // assert(operation.action !== 'create', `${entity as string}上的create动作定义了relation类型的checker,请使用expressionRelation替代`); // 对后台而言,将生成的relationFilter加到filter之上(select可以在此加以权限的过滤) - operation.filter = combineFilters([operation.filter, relationFilter(operation, context, option)]); - return 0; - }) as UpdateTriggerInTxn['fn']; - } - case 'expression': - case 'expressionRelation': { - const { expression, errMsg } = checker; - return (async ({ operation }, context, option) => { - if (context.isRoot() && type === 'expressionRelation') { - return 0; - } - const exprResult = expression(operation, context, option); - if (typeof exprResult === 'string') { - throw new OakUserUnpermittedException(exprResult || errMsg); - } - else if (exprResult === undefined) { - return 0; + 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; + } + throw new OakUserUnpermittedException(errMsg); } else { - const { entity: expressionEntity, expr, filter: expressionFilter } = exprResult; - const [result] = await context.select(expressionEntity, { - data: { - $expr: expr, - }, - filter: expressionFilter, - }, Object.assign({}, option, { dontCollect: true })); - if (!result) { - // 条件判定为假,抛异常 - throw new OakUserUnpermittedException(errMsg); - } + operation.filter = combineFilters([operation.filter, await relationFilter(operation, context, option)]); } return 0; }) as UpdateTriggerInTxn['fn']; + return { + fn, + when, + }; + } + case 'logical': + case 'logicalRelation': { + const { checker: checkerFn } = checker; + const fn = (async ({ operation }, context, option) => { + if (context.isRoot() && type === 'logicalRelation') { + return 0; + } + await checkerFn(operation, context, option); + return 0; + }) as UpdateTriggerInTxn['fn']; + return { + fn, + when, + }; } default: { assert(false); @@ -133,74 +160,85 @@ export function translateCheckerInSyncContext< ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends SyncContext ->(checker: Checker): (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void { - const { entity, type } = checker; +>(checker: Checker): { + 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' switch (type) { case 'data': { const { checker: checkerFn } = checker; - return (operation, context) => checkerFn(operation.data, context); + const fn = (operation: ED[T]['Operation'], context: Cxt) => checkerFn(operation.data, context); + return { + fn, + when, + } } case 'row': { const { filter, errMsg } = checker; - return (operation, context, option) => { + const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => { const { filter: operationFilter, action } = operation; - const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter; + const filter2 = typeof filter === 'function' ? (filter as Function)(operation, context, option) : filter; assert(operationFilter); if (['select', 'count', 'stat'].includes(action)) { operation.filter = addFilterSegment(operationFilter, filter2); return 0; } else { - if (checkFilterContains(entity, context, filter2, operationFilter)) { + assert(!(filter2 instanceof Promise)); + if (checkFilterContains(entity, context, filter2, operationFilter, true)) { return; } throw new OakRowInconsistencyException(undefined, errMsg); } }; + return { + fn, + when, + }; } case 'relation': { - const { relationFilter: filter, errMsg } = checker; - return (operation, context, option) => { + const { relationFilter, errMsg } = checker; + const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => { if (context.isRoot()) { return; } - const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter; - const { filter: operationFilter } = operation; - assert(operationFilter); - if (checkFilterContains(entity, context, filter2, operationFilter)) { + 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 }; + } + assert(filter3); + assert(!(filter2 instanceof Promise)); + if (checkFilterContains(entity, context, filter2, filter3, true)) { return; } throw new OakUserUnpermittedException(errMsg); }; - } - case 'expression': - case 'expressionRelation': { - const { expression, errMsg } = checker; - return (operation, context, option) => { - if (context.isRoot() && type === 'expressionRelation') { - return; - } - const exprResult = expression(operation, context, option); - if (typeof exprResult === 'string') { - throw new OakUserUnpermittedException(exprResult || errMsg); - } - else if (exprResult === undefined) { - return 0; - } - else { - const { entity: expressionEntity, expr, filter: expressionFilter } = exprResult; - const [result] = context.select(expressionEntity, { - data: { - $expr: expr, - }, - filter: expressionFilter, - }, Object.assign({}, option, { dontCollect: true })) as any[]; - if (!result.$expr) { - // 条件判定为假,抛异常 - throw new OakRowInconsistencyException(undefined, errMsg); - } + return { + fn, + when, + }; + } + case 'logical': + case 'logicalRelation': { + const { checker: checkerFn } = checker; + const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => { + if (context.isRoot() && type === 'logicalRelation') { return; } + checkerFn(operation, context, option); + }; + return { + fn, + when, }; } default: { @@ -209,141 +247,232 @@ export function translateCheckerInSyncContext< } } +function translateCascadeRelationFilterMaker( + schema: StorageSchema, + lch: CascadeRelationItem, + entity2: keyof ED): (userId: string) => ED[keyof ED]['Selection']['filter'] { + const { cascadePath, relations } = lch; + const paths = cascadePath.split('.'); -export function createRelationHierarchyCheckers | SyncContext>(schema: StorageSchema) { + const translateRelationFilter = (entity: T): (userId: string) => ED[T]['Selection']['filter'] => { + // 有两种情况,此entity和user有Relation定义,或是此entity上有userId + if (schema[entity].relation) { + const relationEntityName = `user${firstLetterUpperCase(entity as string)}`; + return (userId) => { + const filter = relations ? { + userId, + relation: { + $in: relations, + }, + } : { + userId, + }; + return { + id: { + $in: { + entity: relationEntityName, + data: { + [`${entity as string}Id`]: 1, + }, + filter, + }, + }, + } + }; + } + + const { attributes } = schema[entity]; + assert(attributes.hasOwnProperty('userId') && attributes.userId.type === 'ref' && attributes.userId.ref === 'user', `在${entity as string}上既找不到userId,也没有relation定义`); + return (userId) => ({ + userId, + }); + }; + + const translateFilterMakerIter = (entity: T, iter: number): (userId: string) => ED[T]['Selection']['filter'] => { + const relation = judgeRelation(schema, entity, paths[iter]); + if (iter === paths.length - 1) { + if (relation === 2) { + const filterMaker = translateRelationFilter(paths[iter]); + return (userId) => { + const filter = filterMaker(userId); + if (filter!.$in) { + return { + entity: paths[iter], + entityId: filter, + }; + } + return { + [paths[iter]]: filter, + }; + } + } + assert(typeof relation === 'string'); + const filterMaker = translateRelationFilter(relation); + return (userId) => { + const filter = filterMaker(userId); + + if (filter!.$in) { + return { + [`${paths[iter]}Id`]: filter + }; + } + return { + [paths[iter]]: filter, + }; + } + } + else { + const subFilterMaker = translateFilterMakerIter(paths[iter], iter + 1); + if (iter === 0) { + return (userId) => { + const subFilter = subFilterMaker(userId); + return { + [paths[iter]]: subFilter, + }; + }; + } + return (userId) => ({ + [paths[iter]]: subFilterMaker(userId), + }); + } + }; + + const filter = cascadePath ? translateFilterMakerIter(entity2, 0) : translateRelationFilter(entity2); + return filter; +} + +function translateActionAuthFilterMaker( + schema: StorageSchema, + relationItem: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[], + entity: keyof ED +): (userId: string) => ED[keyof ED]['Selection']['filter'] { + if (relationItem instanceof Array) { + const maker = relationItem.map( + ele => { + if (ele instanceof Array) { + return ele.map( + ele2 => translateCascadeRelationFilterMaker(schema, ele2, entity) + ); + } + return [translateCascadeRelationFilterMaker(schema, ele, entity)]; + } + ); + return (userId) => ({ + $or: maker.map( + ele => ({ + $and: ele.map( + ele2 => ele2(userId)! + ) + }) + ) + }) + } + const filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity); + return (userId) => filterMaker(userId); +} + + +export function createAuthCheckers | SyncContext>( + schema: StorageSchema, + authDict: AuthDefDict) { const checkers: Checker[] = []; for (const entity in schema) { - const { relationHierarchy } = schema[entity]; - if (relationHierarchy) { - // 先build反向hierarchy的map - const reverseHierarchy = {} as Record; - for (const r in relationHierarchy) { - for (const r2 of relationHierarchy[r]!) { - if (!reverseHierarchy[r2]) { - reverseHierarchy[r2] = [r]; - } - else { - reverseHierarchy[r2].push(r); - } + if (authDict[entity]) { + const { relationAuth, actionAuth } = authDict[entity]!; + if (relationAuth) { + const raFilterMakerDict = {} as Record ED[keyof ED]['Selection']['filter']>; + for (const r in relationAuth) { + Object.assign(raFilterMakerDict, { + [r]: translateActionAuthFilterMaker(schema, relationAuth[r as NonNullable]!, entity), + }); } - } - - // 对userEntity对象的授权和回收建立checker - const userEntityName = `user${firstLetterUpperCase(entity)}`; - const entityIdAttr = `${entity}Id`; - checkers.push({ - entity: userEntityName as keyof ED, - action: 'create', - type: 'expressionRelation', - expression: (operation: any, context: Cxt) => { - const { data } = operation as ED[keyof ED]['Operation']; - const { relation, [entityIdAttr]: entityId } = data as Record; - const legalRelations = reverseHierarchy[relation]; - if (!legalRelations) { - return undefined; - } - if (legalRelations.length === 0) { - return '这是不应该跑出来的情况,请杀程序员祭天'; - } - const userId = context.getCurrentUserId(); - return { - entity: userEntityName as T2, - expr: { - $gt: [{ - '#attr': '$$createAt$$', - }, 0] - }, - filter: { - userId, - [entityIdAttr]: entityId, - relation: { - $in: legalRelations, - } - } - } - }, - errMsg: '越权操作', - }); - for (const r in reverseHierarchy) { + const userEntityName = `user${firstLetterUpperCase(entity)}`; + const entityIdAttr = `${entity}Id`; checkers.push({ entity: userEntityName as keyof ED, - action: 'remove', - type: 'expressionRelation', - conditionalFilter: { - relation: r, - } as ED[keyof ED]['Update']['filter'], - expression: (operation: any, context: Cxt) => { + action: 'create', + type: 'relation', + relationFilter: (operation, context) => { + const { data } = operation as ED[keyof ED]['Create']; + assert(!(data instanceof Array)); + const { relation, [entityIdAttr]: entityId } = data; const userId = context.getCurrentUserId(); - const { filter } = operation as ED[keyof ED]['Remove']; - const legalRelations = reverseHierarchy[r]; - if (legalRelations.length === 0) { - return '这是不应该跑出来的情况,请杀程序员祭天'; + if (!raFilterMakerDict[relation]) { + return; } + const filter = raFilterMakerDict[relation]!(userId!); return { - entity: userEntityName as T2, - expr: { - $gt: [{ - '#attr': '$$createAt$$', - }, 0] - }, - filter: { - userId, - [entityIdAttr]: { - $in: { - entity: userEntityName, - data: { - [entityIdAttr]: 1, - }, - filter, - } - }, - relation: { - $in: legalRelations, - } - }, - } + [entity]: filter, + }; }, errMsg: '越权操作', }); + + checkers.push({ + entity: userEntityName as keyof ED, + action: 'remove' as ED[keyof ED]['Action'], + type: 'relation', + relationFilter: (operation: any, context: Cxt) => { + const userId = context.getCurrentUserId(); + const { filter } = operation as ED[keyof ED]['Remove']; + const makeFilterFromRows = (rows: Partial[]) => { + 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 toBeRemoved = context.select(userEntityName, { + data: { + id: 1, + relation: 1, + [entityIdAttr]: 1, + }, + filter, + }, { dontCollect: true }); + if (toBeRemoved instanceof Promise) { + return toBeRemoved.then( + (rows) => makeFilterFromRows(rows) + ); + } + return makeFilterFromRows(toBeRemoved); + }, + errMsg: '越权操作', + }); + // 转让权限现在用update动作,只允许update userId给其它人 + // todo 等实现的时候再写 } - /* // 一个人不能授权给自己,也不能删除自己的授权 - checkers.push({ - entity: userEntityName as keyof ED, - action: 'create' as ED[keyof ED]['Action'], - type: 'data', - checker: (data, context) => { - assert(!(data instanceof Array)); - const { userId } = data as ED[keyof ED]['CreateSingle']['data']; - const userId2 = context.getCurrentUserId(true); - if (userId === userId2) { - throw new OakDataException('不允许授权给自己'); - } + if (actionAuth) { + for (const a in actionAuth) { + const filterMaker = translateActionAuthFilterMaker(schema, actionAuth[a as ED[keyof ED]['Action']]!, entity); + checkers.push({ + entity, + action: a as ED[keyof ED]['Action'], + type: 'relation', + relationFilter: (operation, context) => { + // const { filter } = operation; + const filter = filterMaker(context.getCurrentUserId()!); + return filter; + }, + errMsg: '定义的actionAuth中检查出来越权操作', + }); } - }); - - checkers.push({ - entity: userEntityName as keyof ED, - action: 'remove' as ED[keyof ED]['Action'], - type: 'row', - filter: (operation, context) => { - const userId = context.getCurrentUserId(true); - if (userId) { - return { - userId: { - $ne: userId, - }, - }; - } - console.warn(`没有当前用户但在删除权限,请检查。对象是${entity}`); - return {}; - }, - errMsg: '不允许回收自己的授权', - }); */ - - // 转让权限现在用update动作,只允许update userId给其它人 - // todo 等实现的时候再写 + } } } diff --git a/src/store/filter.ts b/src/store/filter.ts index 2e296f4..b7af2d2 100644 --- a/src/store/filter.ts +++ b/src/store/filter.ts @@ -732,14 +732,14 @@ export function getRelevantIds(filter if (filter?.$and) { const idss = filter.$and.map( - ele => getRelevantIds(ele) + (ele: ED[T]['Selection']['filter']) => getRelevantIds(ele) ); idsAnd = intersection(...idss); } if (filter?.$or) { const idss = filter.$or.map( - ele => getRelevantIds(ele) + (ele: ED[T]['Selection']['filter']) => getRelevantIds(ele) ); idsOr = union(...idss); } @@ -879,11 +879,21 @@ export function makeTreeDescendantFilter | AsyncContext>( entity: T, context: Cxt, contained: ED[T]['Selection']['filter'], - filter?: ED[T]['Selection']['filter']): boolean | Promise { + filter?: ED[T]['Selection']['filter'], + dataCompare?: true): boolean | Promise { if (!filter) { throw new OakRowInconsistencyException(); } @@ -892,29 +902,34 @@ export function checkFilterContains count2 === 0 - ); + if (dataCompare) { + // 再判断加上了conditionalFilter后取得的行数是否缩减 + const filter2 = combineFilters([filter, { + $not: contained, + }]); + const count = context.count(entity, { + filter: filter2, + }, { + dontCollect: true, + blockTrigger: true, + }); + if (count instanceof Promise) { + return count.then( + (count2) => count2 === 0 + ); + } + return count === 0; } - return count === 0; + return false; } export function checkFilterRepel | AsyncContext>( entity: T, context: Cxt, filter1: ED[T]['Selection']['filter'], - filter2: ED[T]['Selection']['filter']): boolean | Promise { + filter2: ED[T]['Selection']['filter'], + dataCompare?: true +): boolean | Promise { if (!filter2) { throw new OakRowInconsistencyException(); } @@ -924,17 +939,20 @@ export function checkFilterRepel count2 === 0 - ); + if (dataCompare) { + const filter3 = combineFilters([filter2, filter1]); + const count = context.count(entity, { + filter: filter3, + }, { + dontCollect: true, + blockTrigger: true, + }); + if (count instanceof Promise) { + return count.then( + (count2) => count2 === 0 + ); + } + return count === 0; } - return count === 0; + return false; } \ No newline at end of file diff --git a/src/store/modi.ts b/src/store/modi.ts index 4a39e25..0c6a10e 100644 --- a/src/store/modi.ts +++ b/src/store/modi.ts @@ -1,6 +1,6 @@ import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { OpSchema as Modi, Filter } from '../base-app-domain/Modi/Schema'; -import { Checker, Operation, StorageSchema, RowChecker, EntityDict, OakRowLockedException, Context, OperateOption, Trigger, RemoveTrigger, RelationChecker, ExpressionChecker, ExpressionRelationChecker, OakUserUnpermittedException } from '../types'; +import { Checker, Operation, StorageSchema, RowChecker, EntityDict, OakRowLockedException, Context, OperateOption, Trigger, RemoveTrigger, RelationChecker, LogicalChecker, LogicalRelationChecker, OakUserUnpermittedException } from '../types'; import { appendOnlyActions } from '../actions/action'; import { difference } from '../utils/lodash'; import { AsyncContext } from './AsyncRowStore'; @@ -52,14 +52,14 @@ export async function abandonModis(schema: StorageSchema< const toBeAssignNode: Record = {}; // 用来记录在表达式中涉及到的结点 // filter当中所关联到的属性必须在projection中 const filterNodeDict: Record = {}; - const checkFilterNode = (entity2: keyof ED, filterNode: DeduceFilter, projectionNode: ED[keyof ED]['Selection']['data']) => { + const checkFilterNode = (entity2: keyof ED, filterNode: ED[keyof ED]['Selection']['filter'], projectionNode: ED[keyof ED]['Selection']['data']) => { const necessaryAttrs: string[] = ['id']; for (const attr in filterNode) { if (attr === '#id') { diff --git a/src/types/Action.ts b/src/types/Action.ts index 8d2aa35..daff3fe 100644 --- a/src/types/Action.ts +++ b/src/types/Action.ts @@ -1,4 +1,5 @@ -import { EntityDict } from "./Entity"; +import { CascadeRelationItem, EntityDict } from "./Entity"; +import { GenericAction } from '../actions/action'; export type Action = string; export type State = string; @@ -14,4 +15,9 @@ export type ActionDictOfEntityDict = { [T in keyof E]?: { [A in keyof E[T]['OpSchema']]?: ActionDef; }; -}; \ No newline at end of file +}; + +// 即在cascadePath指向的对象上,有relation关系。若relation为空则不限定关系 +export type CascadeActionAuth = { + [K in A | GenericAction]?: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[]; +}; diff --git a/src/types/Auth.ts b/src/types/Auth.ts index 887ab3a..c218609 100644 --- a/src/types/Auth.ts +++ b/src/types/Auth.ts @@ -1,9 +1,10 @@ +import { CascadeActionAuth, RelationHierarchy, CascadeRelationAuth } from "."; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption, SelectOption } from "../types/Entity"; import { RefOrExpression } from "./Expression"; -export type CheckerType = 'relation' | 'row' | 'data' | 'expression' | 'expressionRelation'; +export type CheckerType = 'relation' | 'row' | 'data' | 'logical' | 'logicalRelation'; /** * conditionalFilter是指该action发生时,operation所操作的行中有满足conditionalFilter的行 @@ -14,8 +15,10 @@ export type DataChecker | Array>; - checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => void; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => void | Promise; + conditionalFilter?: ED[T]['Update']['filter'] | ( + (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise + ); }; export type RowChecker | SyncContext> = { @@ -23,54 +26,70 @@ 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']); // 对行的额外检查条件 + filter: ED[T]['Selection']['filter'] | ( + (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise + ); // 对行的额外检查条件 errMsg?: string; inconsistentRows?: { // 因为这里的限制不一定在本row上,如果不传这个exception,则默认返回本row上的exception 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']); + conditionalFilter?: ED[T]['Update']['filter'] | ( + (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise + ); }; export type RelationChecker | SyncContext> = { priority?: number; type: 'relation'; entity: T; - action: Omit | Array>; - relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter']; // 生成一个额外的relation相关的filter,加在原先的filter上 + when?: 'after'; + action: ED[T]['Action'] | Array; + relationFilter: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => ED[T]['Selection']['filter'] | Promise; // 生成一个额外的relation相关的filter,加在原先的filter上 errMsg: string; + conditionalFilter?: ED[T]['Update']['filter'] | ( + (operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise + ); +}; + +export type LogicalChecker | SyncContext> = { + priority?: number; + type: 'logical'; + when?: 'after'; + entity: T; + action: ED[T]['Action'] | Array; + checker: ( + operation: ED[T]['Operation'] | ED[T]['Selection'], + context: Cxt, + option: OperateOption | SelectOption + ) => void | Promise; conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); }; -export type ExpressionChecker | SyncContext> = { +export type LogicalRelationChecker | SyncContext> = { priority?: number; - type: 'expression'; + type: 'logicalRelation'; + when?: 'after'; entity: T; action: ED[T]['Action'] | Array; - expression: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => { - entity: T2; - expr: RefOrExpression; - filter: ED[T2]['Selection']['filter']; - } | undefined | string; // 生成一个带表达式的查询任务,结果为true代表可以过,为false不可以。如果返回undefined直接过,返回string直接挂 - errMsg: string; - conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); -}; - - -export type ExpressionRelationChecker | SyncContext> = { - priority?: number; - type: 'expressionRelation'; - entity: T; - action: ED[T]['Action'] | Array; - expression: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => { - entity: T2; - expr: RefOrExpression; - filter: ED[T2]['Selection']['filter']; - } | undefined | string; // 生成一个带表达式的查询任务,结果为true代表可以过。如果返回undefined直接过,返回string直接挂 - errMsg: string; + checker: ( + operation: ED[T]['Operation'] | ED[T]['Selection'], + context: Cxt, + option: OperateOption | SelectOption + ) => void | Promise; conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); }; export type Checker | SyncContext> = - DataChecker | RowChecker | RelationChecker | ExpressionChecker | ExpressionRelationChecker; + DataChecker | RowChecker | RelationChecker | LogicalChecker | LogicalRelationChecker; + + +export type AuthDef = { + relationAuth?: CascadeRelationAuth>; + actionAuth?: CascadeActionAuth; +}; + +export type AuthDefDict = { + [K in keyof ED]?: AuthDef; +}; \ No newline at end of file diff --git a/src/types/Endpoint.ts b/src/types/Endpoint.ts new file mode 100644 index 0000000..07f7fa0 --- /dev/null +++ b/src/types/Endpoint.ts @@ -0,0 +1,11 @@ +import { ClientRequest, IncomingHttpHeaders, IncomingMessage } from "http"; +import { AsyncContext } from "../store/AsyncRowStore"; +import { EntityDict } from "./Entity"; + +export interface Endpoint> { + name: string; + params?: string[]; + method: 'get' | 'post' | 'put' | 'delete'; + fn: (context: BackCxt, params: Record, headers: IncomingHttpHeaders, + req: IncomingMessage, body?: any) => Promise; +}; diff --git a/src/types/Entity.ts b/src/types/Entity.ts index 1cff913..a0eb88a 100644 --- a/src/types/Entity.ts +++ b/src/types/Entity.ts @@ -1,6 +1,3 @@ -import { GenericAction } from '../actions/action'; -import { ExpressionKey, ExprOp, FulltextFilter, MakeFilter, NodeId, Q_BooleanValue, Q_NumberValue, Q_StringValue } from './Demand'; -import { OneOf, OptionalKeys } from './Polyfill'; import { PrimaryKey, Sequence } from './DataType'; type TriggerDataAttributeType = '$$triggerData$$'; @@ -22,7 +19,7 @@ export const SeqAttribute = '$$seq$$'; export type InstinctiveAttributes = PrimaryKeyAttributeType | CreateAtAttributeType | UpdateAtAttributeType| DeleteAtAttributeType | TriggerDataAttributeType | TriggerTimestampAttributeType | SeqAttributeType; export const initinctiveAttributes = [PrimaryKeyAttribute, TriggerDataAttribute, TriggerTimestampAttribute, CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute, SeqAttribute]; -export type Filter = { +type FilterPart = { filter?: A extends 'create' ? undefined : F; indexFrom?: A extends 'create' ? undefined : number; count?: A extends 'create' ? undefined : number; @@ -52,21 +49,17 @@ export type FormUpdateData = Partial<{ [K in keyof Omit]: SH[K] | null; }>; -export type FormCreateData = Omit & { id: string }; +export type FormCreateData = Partial> & { id: string }; -export type Operation = { +export type Operation = { id?: string; // 为了一致性,每个operation也应当保证唯一id action: A; - data: DATA; - sorter?: SORTER; - } & Filter; - -export type Selection = Operation<'select', DATA, FILTER, SORT>; + data: D; + sorter?: S; + } & FilterPart; export interface EntityShape { id: PrimaryKey; @@ -76,9 +69,6 @@ export interface EntityShape { $$deleteAt$$?: number | Date | null; } -export interface FileCarrierEntityShape extends EntityShape { -}; - interface GeneralEntityShape extends EntityShape { [K: string]: any; } @@ -91,33 +81,28 @@ export interface EntityDef { OpSchema: GeneralEntityShape; Action: string; ParticularAction?: string; - Selection: Omit, 'action'>; - Aggregation: Omit, DeduceFilter, DeduceSorter>, 'action'>; - Operation: DeduceOperation; - Create: DeduceCreateOperation; - CreateSingle: DeduceCreateSingleOperation; - CreateMulti: DeduceCreateMultipleOperation; - Update: DeduceUpdateOperation; - Remove: DeduceRemoveOperation; + Selection: Omit, 'action'>; + Aggregation: Omit, 'action'>; + Operation: CUDOperation; + Create: CreateOperation; + CreateSingle: CreateSingleOperation; + CreateMulti: CreateMultipleOperation; + Update: UpdateOperation; + Remove: RemoveOperation; + Relation?: string; }; export interface EntityDict { [E: string]: EntityDef; }; -export interface OtmSubProjection extends Omit, 'action'> { +export interface OtmSubProjection extends Omit, 'action'> { $entity: string; }; -type DeduceProjection = { - '#id'?: NodeId; -} & { - [K in keyof SH]?: number | OtmSubProjection | any; -} & Partial>; - export type AggregationOp = `#max-${number}` | `#min-${number}` | `#avg-${number}` | `#count-${number}` | `#sum-${number}`; -export type DeduceAggregationData> = { +export type DeduceAggregationData

= { [A in AggregationOp]?: P; } & { '#aggr'?: P; @@ -130,57 +115,58 @@ export type AggregationResult = Array<{ }>; export type AttrFilter = { - [K in keyof SH]: any; + [K in keyof SH]?: any; } -export type DeduceFilter = MakeFilter & ExprOp>; +type SortAttr = { + [K: string]: any; +}; -export type DeduceSorterAttr = OneOf<{ - [K: string]: number | object | undefined; -} & ExprOp>; - -export type DeduceSorterItem = { - $attr: DeduceSorterAttr; +type SorterItem = { + $attr: SortAttr $direction?: "asc" | "desc"; }; -export type DeduceSorter = Array>; +type Sorter = Array; -export type DeduceSelection = Selection, DeduceFilter, DeduceSorter>; +type Filter = { + [K: string]: any; +} + +type Projection = { + [K: string]: any; +} export type DeduceAggregation< - SH extends GeneralEntityShape, - P extends DeduceProjection, - F extends DeduceFilter, - S extends DeduceSorter> = Omit, F, S>, 'action'>; + P extends Projection, + F extends Filter, + S extends Sorter> = Omit, F, S>, 'action'>; -export type DeduceCreateOperationData = { +type CreateOperationData = { id: string; -} & { - [k in keyof Omit]?: any; + [K: string]: any; }; -export type DeduceCreateSingleOperation = Operation<'create', DeduceCreateOperationData>; +type CreateSingleOperation = Operation<'create', CreateOperationData, undefined, undefined>; -export type DeduceCreateMultipleOperation = Operation<'create', Array>>; +type CreateMultipleOperation = Operation<'create', Array, undefined, undefined>; -export type DeduceCreateOperation = DeduceCreateSingleOperation | DeduceCreateMultipleOperation; +type CreateOperation = CreateSingleOperation | CreateMultipleOperation; -export type DeduceUpdateOperationData = { - [k in keyof Omit]?: any; -}; +type UpdateOperationData = { + id?: never; + [k: string]: any; +} -export type DeduceUpdateOperation = Operation< - 'update' | string, - DeduceUpdateOperationData, DeduceFilter, DeduceSorter>; +export type UpdateOperation = Operation; -export type DeduceRemoveOperationData = { - [A in keyof Omit]?: any; -}; +type RemoveOperationData = { + [k: string]: any; +} -export type DeduceRemoveOperation = Operation<'remove', DeduceRemoveOperationData, DeduceFilter, DeduceSorter>; +export type RemoveOperation = Operation<'remove', RemoveOperationData, Filter, Sorter>; -export type DeduceOperation = DeduceCreateOperation | DeduceUpdateOperation | DeduceRemoveOperation; +export type CUDOperation = CreateOperation | UpdateOperation | RemoveOperation; export type CreateOpResult = { a: 'c'; @@ -191,20 +177,29 @@ export type CreateOpResult = { export type UpdateOpResult = { a: 'u', e: T; - d: DeduceUpdateOperationData; - f?: DeduceFilter; + d: UpdateOperationData; + f?: Filter; }; export type RemoveOpResult = { a: 'r', e: T; - f?: DeduceFilter; + f?: Filter; }; export type RelationHierarchy = { [K in R]?: R[]; }; +export type CascadeRelationItem = { + cascadePath: string; + relations?: string[]; +}; + +export type CascadeRelationAuth = { + [K in R]?: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[]; +}; + // Select的级联可以去重,压缩返回的数据大小 export type SelectOpResult = { a: 's', @@ -250,20 +245,3 @@ export type Configuration = { actionType?: ActionType; static?: boolean; // 标识是维表(变动较小,相对独立) }; - -export type Exportation = { - name: string; - id: string; - entity: T; - projection: ED[T]['Selection']['data']; - headers: K[]; - fn: (data: ED[T]['Schema']) => Partial>; -}; - -export type Importation = { - name: string; - id: string; - entity: T; - headers: K[]; - fn: (data: Partial>) => ED[T]['CreateSingle']['data']; -}; \ No newline at end of file diff --git a/src/types/Exception.ts b/src/types/Exception.ts index b97fcbb..8ef5b78 100644 --- a/src/types/Exception.ts +++ b/src/types/Exception.ts @@ -26,6 +26,18 @@ export class OakDataException extends OakException { // 表示由数据层发现的异常 } +export class OakImportDataParseException extends OakException { + line: number; + header?: string; + + // message必传,描述具体错误的数据内容 + constructor(message: string, line: number, header?: string) { + super(message); + this.line = line; + this.header = header; + } +} + export class OakOperExistedException extends OakDataException { // 进行操作时发现同样id的Oper对象已经存在 } @@ -220,6 +232,9 @@ export function makeException(data: { case 'OakDeadlock': { return new OakDeadlock(data.message); } + case 'OakImportDataParseException': { + return new OakImportDataParseException(data.message!, data.line, data.header); + } default: return; } diff --git a/src/types/Expression.ts b/src/types/Expression.ts index 1657d80..7425586 100644 --- a/src/types/Expression.ts +++ b/src/types/Expression.ts @@ -16,7 +16,7 @@ export type RefOrExpression = RefAttr | Expression; type MathType = RefOrExpression | number; type StringType = RefOrExpression | string interface Add { - $add: (MathType | StringType)[]; + $add: (MathType)[]; }; interface Subtract { $subtract: [MathType, MathType]; @@ -135,6 +135,15 @@ interface DateFloor { type DateExpression = DateYear | DateMonth | DateWeekday | DateWeekOfYear | DateDay | DateDayOfYear | DateDayOfMonth | DateDayOfWeek | DateDiff | DateCeiling | DateFloor; +// String +interface StringConcat { + $concat: StringType[]; +} + +type StringExpression = StringConcat; + + + //// Geo interface GeoContains { $contains: [RefOrExpression | Geo, RefOrExpression | Geo]; @@ -145,7 +154,31 @@ interface GeoDistance { type GeoExpression = GeoContains | GeoDistance; -export type Expression = GeoExpression | DateExpression | LogicExpression | BoolExpression | CompareExpression | MathExpression; +//// Aggr +interface AggrCountExpression { + $$count: RefOrExpression; +}; + +interface AggrSumExpression { + $$sum: RefOrExpression; +} + +interface AggrMaxExpression { + $$max: RefOrExpression; +} + +interface AggrMinExpression { + $$min: RefOrExpression; +} + +interface AggrAvgExpression { + $$avg: RefOrExpression; +} + +export type AggrExpression = AggrAvgExpression | AggrCountExpression | AggrSumExpression | AggrMaxExpression | AggrMinExpression; + +export type Expression = GeoExpression | DateExpression | LogicExpression + | BoolExpression | CompareExpression | MathExpression | StringExpression | AggrExpression; export type ExpressionConstant = Geo | number | Date | string | boolean; @@ -212,13 +245,35 @@ export function isMathExpression(expression: any): expression is MathExpressi return false; } + +export function isStringExpression(expression: any): expression is StringExpression { + if (Object.keys(expression).length == 1) { + const op = Object.keys(expression)[0]; + if (['$concat'].includes(op)) { + return true; + } + } + return false; +} + +export function isAggrExpression(expression: any): expression is AggrExpression { + if (Object.keys(expression).length == 1) { + const op = Object.keys(expression)[0]; + if (['$$max', '$$min', '$$sum', '$$avg', '$$count'].includes(op)) { + return true; + } + } + return false; +} + export function isExpression(expression: any): expression is Expression { return typeof expression === 'object' && Object.keys(expression).length === 1 && Object.keys(expression)[0].startsWith('$'); } export function opMultipleParams(op: string) { return !['$year', '$month', '$weekday', '$weekOfYear', '$day', '$dayOfMonth', - '$dayOfWeek', '$dayOfYear', '$not', '$true', '$false', '$abs', '$round', '$floor', '$ceil'].includes(op); + '$dayOfWeek', '$dayOfYear', '$not', '$true', '$false', '$abs', + '$round', '$floor', '$ceil', '$$max', '$$min', '$$sum', '$$avg', '$$count'].includes(op); } export function execOp(op: string, params: any, obscure?: boolean): ExpressionConstant { @@ -435,6 +490,9 @@ export function execOp(op: string, params: any, obscure?: boolean): ExpressionCo case '$contains': { throw new Error('$contains类型未实现'); } + case '$concat': { + return params.join(''); + } default: { assert(false, `不能识别的expression运算符:${op}`); } diff --git a/src/types/Port.ts b/src/types/Port.ts new file mode 100644 index 0000000..bb35b31 --- /dev/null +++ b/src/types/Port.ts @@ -0,0 +1,21 @@ +import { AsyncContext } from "../store/AsyncRowStore"; +import { SyncContext } from "../store/SyncRowStore"; +import { EntityDict } from "./Entity"; + +export type Exportation = { + name: string; + id: string; + entity: T; + projection: ED[T]['Selection']['data']; + headers: K[]; + fn: (data: ED[T]['Schema']) => Partial>; +}; + +export type Importation = { + name: string; + id: string; + entity: T; + headers: K[]; + // 解析过程中如果出错,请抛出OakImportDataParseException异常 + fn: (data: Partial>[], context: AsyncContext, option?: Record ) => Promise; +}; \ No newline at end of file diff --git a/src/types/Storage.ts b/src/types/Storage.ts index 234253c..6b4f1ec 100644 --- a/src/types/Storage.ts +++ b/src/types/Storage.ts @@ -1,5 +1,5 @@ import { ActionType } from '.'; -import { EntityDict, EntityShape, InstinctiveAttributes, RelationHierarchy } from './Entity'; +import { EntityDict, EntityShape, InstinctiveAttributes, RelationHierarchy, CascadeRelationAuth } from './Entity'; import { DataType, DataTypeParams } from './schema/DataTypes'; export type Ref = 'ref'; @@ -47,7 +47,7 @@ export type UniqConstraint = { type?: string; }; -export interface StorageDesc { +export interface StorageDesc { storageName?: string, comment?: string, attributes: Attributes; @@ -59,12 +59,14 @@ export interface StorageDesc; + [K in keyof ED]: StorageDesc; } diff --git a/src/types/Trigger.ts b/src/types/Trigger.ts index de16e0d..8cae331 100644 --- a/src/types/Trigger.ts +++ b/src/types/Trigger.ts @@ -39,7 +39,7 @@ export interface UpdateTriggerBase; check?: (operation: ED[T]['Update']) => boolean; fn: (event: { operation: ED[T]['Update'] }, context: Cxt, option: OperateOption) => Promise | number; - filter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Update'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']); + filter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Update'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise); }; export interface UpdateTriggerInTxn | SyncContext> extends UpdateTriggerBase { @@ -63,7 +63,7 @@ export interface RemoveTriggerBase boolean; fn: (event: { operation: ED[T]['Remove'] }, context: Cxt, option: OperateOption) => Promise | number; - filter?: ED[T]['Remove']['filter'] | ((operation: ED[T]['Remove'], context: Cxt, option: OperateOption) => ED[T]['Remove']['filter']); + filter?: ED[T]['Remove']['filter'] | ((operation: ED[T]['Remove'], context: Cxt, option: OperateOption) => ED[T]['Remove']['filter'] | Promise); }; export interface RemoveTriggerInTxn | SyncContext> extends RemoveTriggerBase { diff --git a/src/types/index.ts b/src/types/index.ts index 6ff1a97..dcb0909 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,4 +16,6 @@ export * from './Exception'; export * from './Watcher'; export * from './AppLoader'; export * from './Connector'; -export * from './Timer'; \ No newline at end of file +export * from './Timer'; +export * from './Port'; +export * from './Endpoint'; diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..8f3e66d --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,14 @@ +import dayjs from 'dayjs'; + +export function excelStringToDate(str: string | number) { + if (!str) { + return undefined; + } + if (typeof str === 'number') { + if (str < 100000) { + return dayjs((((str - 25569) * 24 - 8) * 3600) * 1000).valueOf(); // excel日期可能为1900-1-1至今的天数 + } + return dayjs(str).valueOf(); + } + return Date.parse(str); +} \ No newline at end of file