From 51b46001472047ae52d97ac9a4c5fd21b6ccad63 Mon Sep 17 00:00:00 2001 From: "Xc@centOs" Date: Mon, 29 Aug 2022 15:53:47 +0800 Subject: [PATCH] =?UTF-8?q?modi=E7=9B=B8=E5=85=B3=E7=9A=84checkers?= =?UTF-8?q?=E4=BB=A5=E5=8F=8Aschema=E4=B8=ADactionType=E7=9A=84=E7=BB=86?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/actions/action.d.ts | 8 +- lib/actions/action.js | 7 +- lib/base-app-domain/Modi/Action.d.ts | 1 + lib/base-app-domain/Modi/Action.js | 3 +- lib/base-app-domain/Modi/Storage.js | 3 + lib/base-app-domain/ModiEntity/Storage.js | 5 +- lib/base-app-domain/Oper/Storage.js | 5 +- lib/base-app-domain/OperEntity/Storage.js | 5 +- lib/base-app-domain/User/Storage.js | 5 +- lib/checkers/index.d.ts | 3 + lib/checkers/index.js | 8 + lib/compiler/schemalBuilder.js | 44 +++++- lib/entities/ModiEntity.js | 1 + lib/entities/Oper.js | 1 + lib/entities/OperEntity.js | 1 + lib/store/CascadeStore.js | 2 +- lib/store/modi.d.ts | 9 +- lib/store/modi.js | 128 +++++++++++++++- lib/triggers/modi.js | 4 +- lib/types/Entity.d.ts | 5 +- lib/types/Exception.d.ts | 6 + lib/types/Exception.js | 17 ++- lib/types/Storage.d.ts | 3 + lib/utils/lodash.d.ts | 3 +- lib/utils/lodash.js | 4 +- src/actions/action.ts | 11 +- src/checkers/index.ts | 7 + src/compiler/schemalBuilder.ts | 175 +++++++++++++++++++--- src/entities/ModiEntity.ts | 3 +- src/entities/Oper.ts | 4 +- src/entities/OperEntity.ts | 3 +- src/store/CascadeStore.ts | 2 +- src/store/modi.ts | 98 +++++++++++- src/triggers/modi.ts | 4 +- src/types/Entity.ts | 6 +- src/types/Exception.ts | 12 ++ src/types/Storage.ts | 5 +- src/utils/lodash.ts | 2 + 38 files changed, 559 insertions(+), 54 deletions(-) create mode 100644 lib/checkers/index.d.ts create mode 100644 lib/checkers/index.js create mode 100644 src/checkers/index.ts diff --git a/lib/actions/action.d.ts b/lib/actions/action.d.ts index 601f123..10ed137 100644 --- a/lib/actions/action.d.ts +++ b/lib/actions/action.d.ts @@ -1,5 +1,11 @@ import { ActionDef } from '../types/Action'; -export declare type GenericAction = 'create' | 'update' | 'remove' | 'select' | 'count' | 'stat' | 'download'; +export declare type ReadOnlyAction = 'select' | 'count' | 'stat' | 'download'; +export declare type AppendOnlyAction = ReadOnlyAction | 'create'; +export declare type ExcludeUpdateAction = AppendOnlyAction | 'remove'; +export declare type GenericAction = 'update' | ExcludeUpdateAction; +export declare const readOnlyActions: string[]; +export declare const appendOnlyActions: string[]; +export declare const excludeUpdateActions: string[]; export declare const genericActions: string[]; export declare type AbleAction = 'enable' | 'disable'; export declare type AbleState = 'enabled' | 'disabled'; diff --git a/lib/actions/action.js b/lib/actions/action.js index 359d12d..d6a237b 100644 --- a/lib/actions/action.js +++ b/lib/actions/action.js @@ -1,7 +1,10 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.makeAbleActionDef = exports.genericActions = void 0; -exports.genericActions = ['create', 'update', 'remove', 'count', 'stat', 'download', 'select']; +exports.makeAbleActionDef = exports.genericActions = exports.excludeUpdateActions = exports.appendOnlyActions = exports.readOnlyActions = void 0; +exports.readOnlyActions = ['count', 'stat', 'download', 'select']; +exports.appendOnlyActions = exports.readOnlyActions.concat('create'); +exports.excludeUpdateActions = exports.appendOnlyActions.concat('remove'); +exports.genericActions = exports.excludeUpdateActions.concat('update'); var makeAbleActionDef = function (initialState) { return ({ stm: { enable: ['disabled', 'enabled'], diff --git a/lib/base-app-domain/Modi/Action.d.ts b/lib/base-app-domain/Modi/Action.d.ts index 19095d5..e22999c 100644 --- a/lib/base-app-domain/Modi/Action.d.ts +++ b/lib/base-app-domain/Modi/Action.d.ts @@ -4,6 +4,7 @@ export declare type IState = 'active' | 'applied' | 'abandoned'; export declare type IAction = 'apply' | 'abandon'; export declare type ParticularAction = IAction; export declare type Action = GenericAction | ParticularAction; +export declare const actions: string[]; export declare const ActionDefDict: { iState: ActionDef; }; diff --git a/lib/base-app-domain/Modi/Action.js b/lib/base-app-domain/Modi/Action.js index 3e934ab..745f0a3 100644 --- a/lib/base-app-domain/Modi/Action.js +++ b/lib/base-app-domain/Modi/Action.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.ActionDefDict = void 0; +exports.ActionDefDict = exports.actions = void 0; var IActionDef = { stm: { apply: ['active', 'applied'], @@ -8,6 +8,7 @@ var IActionDef = { }, is: 'active' }; +exports.actions = ["count", "stat", "download", "select", "create", "remove", "update", "apply", "abandon"]; exports.ActionDefDict = { iState: IActionDef }; diff --git a/lib/base-app-domain/Modi/Storage.js b/lib/base-app-domain/Modi/Storage.js index 973fc6b..ddd48d2 100644 --- a/lib/base-app-domain/Modi/Storage.js +++ b/lib/base-app-domain/Modi/Storage.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.desc = void 0; +var Action_1 = require("./Action"); exports.desc = { attributes: { targetEntity: { @@ -43,6 +44,8 @@ exports.desc = { } } }, + actionType: "crud", + actions: Action_1.actions, indexes: [ { name: 'index_state', diff --git a/lib/base-app-domain/ModiEntity/Storage.js b/lib/base-app-domain/ModiEntity/Storage.js index bd6e90c..5cc06a0 100644 --- a/lib/base-app-domain/ModiEntity/Storage.js +++ b/lib/base-app-domain/ModiEntity/Storage.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.desc = void 0; +var action_1 = require("../../actions/action"); exports.desc = { attributes: { modiId: { @@ -19,5 +20,7 @@ exports.desc = { length: 64 } } - } + }, + actionType: "appendOnly", + actions: action_1.appendOnlyActions }; diff --git a/lib/base-app-domain/Oper/Storage.js b/lib/base-app-domain/Oper/Storage.js index a3b5520..b21ecf3 100644 --- a/lib/base-app-domain/Oper/Storage.js +++ b/lib/base-app-domain/Oper/Storage.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.desc = void 0; +var action_1 = require("../../actions/action"); exports.desc = { attributes: { action: { @@ -22,5 +23,7 @@ exports.desc = { type: "ref", ref: "user" } - } + }, + actionType: "appendOnly", + actions: action_1.appendOnlyActions }; diff --git a/lib/base-app-domain/OperEntity/Storage.js b/lib/base-app-domain/OperEntity/Storage.js index b0e492a..721a38e 100644 --- a/lib/base-app-domain/OperEntity/Storage.js +++ b/lib/base-app-domain/OperEntity/Storage.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.desc = void 0; +var action_1 = require("../../actions/action"); exports.desc = { attributes: { operId: { @@ -19,5 +20,7 @@ exports.desc = { length: 64 } } - } + }, + actionType: "appendOnly", + actions: action_1.appendOnlyActions }; diff --git a/lib/base-app-domain/User/Storage.js b/lib/base-app-domain/User/Storage.js index ade95fc..05cdbfa 100644 --- a/lib/base-app-domain/User/Storage.js +++ b/lib/base-app-domain/User/Storage.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.desc = void 0; +var action_1 = require("../../actions/action"); exports.desc = { attributes: { name: { @@ -18,5 +19,7 @@ exports.desc = { password: { type: "text" } - } + }, + actionType: "crud", + actions: action_1.genericActions }; diff --git a/lib/checkers/index.d.ts b/lib/checkers/index.d.ts new file mode 100644 index 0000000..9ca8cfa --- /dev/null +++ b/lib/checkers/index.d.ts @@ -0,0 +1,3 @@ +import { EntityDict } from '../base-app-domain'; +import { StorageSchema, EntityDict as BaseEntityDict, Context } from '../types'; +export declare function createCheckers>(schema: StorageSchema): import("../types").Checker[]; diff --git a/lib/checkers/index.js b/lib/checkers/index.js new file mode 100644 index 0000000..dc981e3 --- /dev/null +++ b/lib/checkers/index.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createCheckers = void 0; +var modi_1 = require("../store/modi"); +function createCheckers(schema) { + return (0, modi_1.createModiRelatedCheckers)(schema); +} +exports.createCheckers = createCheckers; diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index 592fc74..79e2ce3 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -199,7 +199,7 @@ var RESERVED_ACTION_NAMES = ['GenericAction', 'ParticularAction']; var action_1 = require("../actions/action"); var DataType_1 = require("../types/DataType"); var Entity_1 = require("../types/Entity"); -function dealWithActions(moduleName, filename, node, program) { +function dealWithActions(moduleName, filename, node, program, sourceFile) { var actionTexts = action_1.genericActions.map(function (ele) { return ele; }); if (ts.isUnionTypeNode(node)) { var actionNames = node.types.map(function (ele) { @@ -243,8 +243,7 @@ function dealWithActions(moduleName, filename, node, program) { _a)); } }); - // 为每个action在schema中建立相应的state域(除了genericState) - // 放到actionDef的定义处去做。by Xc + pushStatementIntoActionAst(moduleName, factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("actions"), undefined, undefined, factory.createArrayLiteralExpression(actionTexts.map(function (ele) { return factory.createStringLiteral(ele); }), false))], ts.NodeFlags.Const)), sourceFile); } function getEntityImported(declaration, filename) { var moduleSpecifier = declaration.moduleSpecifier, importClause = declaration.importClause; @@ -315,6 +314,7 @@ function analyzeEntity(filename, path, program) { var hasRelationDef = false; var hasActionOrStateDef = false; var toModi = false; + var actionType = 'crud'; var enumStringAttrs = []; var states = []; var localEnumStringTypes = []; @@ -484,7 +484,7 @@ function analyzeEntity(filename, path, program) { factory.createTypeReferenceNode(factory.createIdentifier("GenericAction"), undefined), factory.createTypeReferenceNode(factory.createIdentifier("ParticularAction"), undefined) ])), sourceFile); - dealWithActions(moduleName, filename, node.type, program); + dealWithActions(moduleName, filename, node.type, program, sourceFile); } else if (node.name.text === 'Relation') { (0, assert_1.default)(!localeDef, "\u3010".concat(filename, "\u3011locale\u5B9A\u4E49\u987B\u5728Relation\u4E4B\u540E")); @@ -688,11 +688,10 @@ function analyzeEntity(filename, path, program) { }); indexes = declaration.initializer; } - else if (ts.isIdentifier(declaration.name) && declaration.name.text === 'locale') { + else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { // locale定义 var type = declaration.type, initializer = declaration.initializer; (0, assert_1.default)(ts.isObjectLiteralExpression(initializer)); - (0, assert_1.default)(ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === 'LocaleDef', 'locale定义的类型必须是LocaleDef'); var properties = initializer.properties; (0, assert_1.default)(properties.length > 0, "".concat(filename, "\u81F3\u5C11\u9700\u8981\u6709\u4E00\u79CDlocale\u5B9A\u4E49")); var allEnumStringAttrs = enumStringAttrs.concat(states); @@ -737,6 +736,10 @@ function analyzeEntity(filename, path, program) { } localeDef = initializer; } + else if (ts.isTypeReferenceNode(declaration.type) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ActionType') { + (0, assert_1.default)(ts.isStringLiteral(declaration.initializer)); + actionType = declaration.initializer.text; + } else { throw new Error("\u4E0D\u80FD\u7406\u89E3\u7684\u5B9A\u4E49\u5185\u5BB9".concat(declaration.name.getText())); } @@ -746,11 +749,15 @@ function analyzeEntity(filename, path, program) { if (!hasActionDef && hasActionOrStateDef) { throw new Error("".concat(filename, "\u4E2D\u6709Action\u6216State\u5B9A\u4E49\uFF0C\u4F46\u6CA1\u6709\u5B9A\u4E49\u5B8C\u6574\u7684Action\u7C7B\u578B")); } + if (hasActionDef && actionType !== 'crud') { + throw new Error("".concat(filename, "\u4E2D\u6709Action\u5B9A\u4E49\uFF0C\u4F46\u5374\u5B9A\u4E49\u4E86actionType\u4E0D\u662Fcrud")); + } (0, assert_1.default)(schemaAttrs.length > 0); var schema = { schemaAttrs: schemaAttrs, sourceFile: sourceFile, toModi: toModi, + actionType: actionType, }; if (hasFulltextIndex) { (0, lodash_1.assign)(schema, { @@ -2639,11 +2646,33 @@ function outputStorage(outputDir, printer) { var entityAssignments = []; for (var entity in Schema) { var indexExpressions = []; - var _a = Schema[entity], sourceFile = _a.sourceFile, fulltextIndex = _a.fulltextIndex, indexes = _a.indexes, toModi = _a.toModi; + var _a = Schema[entity], sourceFile = _a.sourceFile, fulltextIndex = _a.fulltextIndex, indexes = _a.indexes, toModi = _a.toModi, actionType = _a.actionType; 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([factory.createImportSpecifier(false, undefined, factory.createIdentifier("OpSchema"))])), factory.createStringLiteral("./Schema"), undefined) ]; + switch (actionType) { + case 'readOnly': { + statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("readOnlyActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined)); + break; + } + case 'appendOnly': { + statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("appendOnlyActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined)); + break; + } + case 'excludeUpdate': { + statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("excludeUpdateActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined)); + break; + } + default: { + if (ActionAsts[entity]) { + statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("actions"))])), factory.createStringLiteral("./Action"), undefined)); + } + else { + statements.push(factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("genericActions"), factory.createIdentifier("actions"))])), factory.createStringLiteral((0, env_1.ACTION_CONSTANT_IN_OAK_DOMAIN)()), undefined)); + } + } + } var propertyAssignments = []; var attributes = constructAttributes(entity); propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("attributes"), factory.createObjectLiteralExpression(attributes, true))); @@ -2653,6 +2682,7 @@ function outputStorage(outputDir, printer) { if (toModi) { propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("toModi"), factory.createTrue())); } + propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("actionType"), factory.createStringLiteral(actionType)), factory.createShorthandPropertyAssignment(factory.createIdentifier("actions"), undefined)); if (indexExpressions.length > 0) { propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("indexes"), factory.createArrayLiteralExpression(indexExpressions, true))); } diff --git a/lib/entities/ModiEntity.js b/lib/entities/ModiEntity.js index ddcebb3..ce0a449 100644 --- a/lib/entities/ModiEntity.js +++ b/lib/entities/ModiEntity.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); ; +var actionType = 'appendOnly'; var locale = { zh_CN: { attr: { diff --git a/lib/entities/Oper.js b/lib/entities/Oper.js index 79a329a..d1923e9 100644 --- a/lib/entities/Oper.js +++ b/lib/entities/Oper.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); ; +var actionType = 'appendOnly'; var locale = { zh_CN: { attr: { diff --git a/lib/entities/OperEntity.js b/lib/entities/OperEntity.js index ce75c9c..0975d04 100644 --- a/lib/entities/OperEntity.js +++ b/lib/entities/OperEntity.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); ; +var actionType = 'appendOnly'; var locale = { zh_CN: { attr: { diff --git a/lib/store/CascadeStore.js b/lib/store/CascadeStore.js index 529ad68..db9af56 100644 --- a/lib/store/CascadeStore.js +++ b/lib/store/CascadeStore.js @@ -723,7 +723,7 @@ var CascadeStore = /** @class */ (function (_super) { } return [3 /*break*/, 23]; case 1: - if (!(option.modiParentEntity && !['modi', 'modiEntity'].includes(entity))) return [3 /*break*/, 3]; + if (!(option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity))) return [3 /*break*/, 3]; modiCreate = { id: 'dummy', action: 'create', diff --git a/lib/store/modi.d.ts b/lib/store/modi.d.ts index 3f44c55..60e82c9 100644 --- a/lib/store/modi.d.ts +++ b/lib/store/modi.d.ts @@ -1,6 +1,11 @@ -import { OpSchema as Modi } from '../base-app-domain/Modi/Schema'; -import { Operation } from '../types'; +import { EntityDict } from '../base-app-domain'; +import { UniversalContext } from '../store/UniversalContext'; +import { OpSchema as Modi, Filter } from '../base-app-domain/Modi/Schema'; +import { Checker, Operation, StorageSchema, EntityDict as BaseEntityDict, Context } from '../types'; export declare function createOperationsFromModies(modies: Modi[]): Array<{ operation: Operation; entity: string; }>; +export declare function applyModis>(filter: Filter, context: Cxt): Promise>; +export declare function abandonModis>(filter: Filter, context: Cxt): Promise>; +export declare function createModiRelatedCheckers>(schema: StorageSchema): Checker[]; diff --git a/lib/store/modi.js b/lib/store/modi.js index 130c043..bbc441a 100644 --- a/lib/store/modi.js +++ b/lib/store/modi.js @@ -1,6 +1,10 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.createOperationsFromModies = void 0; +exports.createModiRelatedCheckers = exports.abandonModis = exports.applyModis = exports.createOperationsFromModies = void 0; +var tslib_1 = require("tslib"); +var types_1 = require("../types"); +var action_1 = require("../actions/action"); +var lodash_1 = require("../utils/lodash"); function createOperationsFromModies(modies) { return modies.map(function (modi) { return { @@ -15,3 +19,125 @@ function createOperationsFromModies(modies) { }); } exports.createOperationsFromModies = createOperationsFromModies; +function applyModis(filter, context) { + return tslib_1.__awaiter(this, void 0, void 0, function () { + var _a, _b, _c; + var _d; + return tslib_1.__generator(this, function (_e) { + switch (_e.label) { + case 0: + _b = (_a = context.rowStore).operate; + _c = ['modi']; + _d = {}; + return [4 /*yield*/, generateNewId()]; + case 1: return [2 /*return*/, _b.apply(_a, _c.concat([(_d.id = _e.sent(), + _d.action = 'apply', + _d.data = {}, + _d.filter = filter, + _d.sorter = [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'asc', + } + ], + _d), context, { + dontCollect: true, + blockTrigger: true, + }]))]; + } + }); + }); +} +exports.applyModis = applyModis; +function abandonModis(filter, context) { + return tslib_1.__awaiter(this, void 0, void 0, function () { + var _a, _b, _c; + var _d; + return tslib_1.__generator(this, function (_e) { + switch (_e.label) { + case 0: + _b = (_a = context.rowStore).operate; + _c = ['modi']; + _d = {}; + return [4 /*yield*/, generateNewId()]; + case 1: return [2 /*return*/, _b.apply(_a, _c.concat([(_d.id = _e.sent(), + _d.action = 'abadon', + _d.data = {}, + _d.filter = filter, + _d.sorter = [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'asc', + } + ], + _d), context, { + dontCollect: true, + blockTrigger: true, + }]))]; + } + }); + }); +} +exports.abandonModis = abandonModis; +function createModiRelatedCheckers(schema) { + var _this = this; + var checkers = []; + var _loop_1 = function (entity) { + var _a = schema[entity], actionType = _a.actionType, actions = _a.actions; + if (['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity) || ['readOnly', 'appendOnly'].includes(actionType)) { + return "continue"; + } + var restActions = (0, lodash_1.difference)(actions, action_1.appendOnlyActions); + checkers.push({ + entity: entity, + action: restActions, + type: 'row', + checker: function (_a, context) { + var operation = _a.operation; + return tslib_1.__awaiter(_this, void 0, void 0, function () { + var filter, filter2, count; + var _b; + return tslib_1.__generator(this, function (_c) { + switch (_c.label) { + case 0: + filter = operation.filter; + filter2 = { + modi: { + iState: 'active', + }, + }; + if (filter) { + Object.assign(filter2, (_b = {}, + _b[entity] = filter, + _b)); + } + else { + Object.assign(filter2, { + entity: entity, + }); + } + return [4 /*yield*/, context.rowStore.count('modiEntity', { + filter: filter2, + }, context, {})]; + case 1: + count = _c.sent(); + if (count > 0) { + throw new types_1.OakRowLockedException(); + } + return [2 /*return*/, 0]; + } + }); + }); + }, + }); + }; + for (var entity in schema) { + _loop_1(entity); + } + return checkers; +} +exports.createModiRelatedCheckers = createModiRelatedCheckers; diff --git a/lib/triggers/modi.js b/lib/triggers/modi.js index f2b9c6a..9147d56 100644 --- a/lib/triggers/modi.js +++ b/lib/triggers/modi.js @@ -42,7 +42,9 @@ var triggers = [ action: action, data: data, filter: filter_1, - }, context, option)]; + }, context, Object.assign({}, option, { + blockTrigger: true, + }))]; case 4: _c.sent(); _c.label = 5; diff --git a/lib/types/Entity.d.ts b/lib/types/Entity.d.ts index 7d85b31..d8ea81d 100644 --- a/lib/types/Entity.d.ts +++ b/lib/types/Entity.d.ts @@ -13,13 +13,14 @@ export declare type Filter; export declare type Selection = Omit, 'id'>; export interface EntityShape { @@ -143,4 +143,5 @@ export declare type SelectRowShape> = { result: Array>; }; +export declare type ActionType = 'readOnly' | 'appendOnly' | 'excludeUpdate' | 'crud'; export {}; diff --git a/lib/types/Exception.d.ts b/lib/types/Exception.d.ts index 338d85a..eba73c4 100644 --- a/lib/types/Exception.d.ts +++ b/lib/types/Exception.d.ts @@ -42,6 +42,12 @@ export declare class OakUserUnpermittedException extends OakUserException { export declare class OakUnloggedInException extends OakUserException { constructor(message?: string); } +/** + * 用户未登录抛的异常 + */ +export declare class OakRowLockedException extends OakUserException { + constructor(message?: string); +} /** * 要插入行时,发现已经有相同的行数据 */ diff --git a/lib/types/Exception.js b/lib/types/Exception.js index 3deb6a3..7c20d66 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.OakCongruentRowExists = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakOperExistedException = exports.OakDataException = exports.OakException = void 0; +exports.makeException = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakOperExistedException = exports.OakDataException = exports.OakException = void 0; var tslib_1 = require("tslib"); var OakException = /** @class */ (function (_super) { tslib_1.__extends(OakException, _super); @@ -138,6 +138,18 @@ var OakUnloggedInException = /** @class */ (function (_super) { }(OakUserException)); exports.OakUnloggedInException = OakUnloggedInException; ; +/** + * 用户未登录抛的异常 + */ +var OakRowLockedException = /** @class */ (function (_super) { + tslib_1.__extends(OakRowLockedException, _super); + function OakRowLockedException(message) { + return _super.call(this, message || '该行数据正在被更新中,请稍后再试') || this; + } + return OakRowLockedException; +}(OakUserException)); +exports.OakRowLockedException = OakRowLockedException; +; /** * 要插入行时,发现已经有相同的行数据 */ @@ -188,6 +200,9 @@ function makeException(data) { case OakCongruentRowExists.name: { return new OakCongruentRowExists(data.data, data.message); } + case OakRowLockedException.name: { + return new OakRowLockedException(data.message); + } default: return; } diff --git a/lib/types/Storage.d.ts b/lib/types/Storage.d.ts index d888764..d6347f4 100644 --- a/lib/types/Storage.d.ts +++ b/lib/types/Storage.d.ts @@ -1,3 +1,4 @@ +import { ActionType } from '.'; import { EntityDict, EntityShape, InstinctiveAttributes } from './Entity'; import { DataType, DataTypeParams } from './schema/DataTypes'; export declare type Ref = 'ref'; @@ -42,6 +43,8 @@ export interface StorageDesc { indexes?: Index[]; config?: EntityConfig; toModi?: true; + actions: string[]; + actionType: ActionType; view?: true; } export declare type StorageSchema = { diff --git a/lib/utils/lodash.d.ts b/lib/utils/lodash.d.ts index 783db58..8c32a2e 100644 --- a/lib/utils/lodash.d.ts +++ b/lib/utils/lodash.d.ts @@ -14,4 +14,5 @@ import cloneDeep from 'lodash/cloneDeep'; import pick from 'lodash/pick'; import isEqual from 'lodash/isEqual'; import union from 'lodash/union'; -export { unset, pull, uniq, get, set, intersection, omit, merge, cloneDeep, pick, isEqual, union, }; +import difference from 'lodash/difference'; +export { unset, pull, uniq, get, set, intersection, omit, merge, cloneDeep, pick, isEqual, union, difference, }; diff --git a/lib/utils/lodash.js b/lib/utils/lodash.js index 2204448..e3682eb 100644 --- a/lib/utils/lodash.js +++ b/lib/utils/lodash.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.union = exports.isEqual = exports.pick = exports.cloneDeep = exports.merge = exports.omit = exports.intersection = exports.set = exports.get = exports.uniq = exports.pull = exports.unset = void 0; +exports.difference = exports.union = exports.isEqual = exports.pick = exports.cloneDeep = exports.merge = exports.omit = exports.intersection = exports.set = exports.get = exports.uniq = exports.pull = exports.unset = void 0; var tslib_1 = require("tslib"); /** * 避免lodash打包体积过大 @@ -30,3 +30,5 @@ var isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual")); exports.isEqual = isEqual_1.default; var union_1 = tslib_1.__importDefault(require("lodash/union")); exports.union = union_1.default; +var difference_1 = tslib_1.__importDefault(require("lodash/difference")); +exports.difference = difference_1.default; diff --git a/src/actions/action.ts b/src/actions/action.ts index bd7831c..666a22a 100644 --- a/src/actions/action.ts +++ b/src/actions/action.ts @@ -1,6 +1,13 @@ import { ActionDef } from '../types/Action'; -export type GenericAction = 'create' | 'update' | 'remove' | 'select' | 'count' | 'stat' | 'download'; -export const genericActions = ['create', 'update', 'remove', 'count', 'stat', 'download', 'select']; +export type ReadOnlyAction = 'select' | 'count' | 'stat' | 'download'; +export type AppendOnlyAction = ReadOnlyAction | 'create'; +export type ExcludeUpdateAction = AppendOnlyAction | 'remove'; +export type GenericAction = 'update' | ExcludeUpdateAction; + +export const readOnlyActions = ['count', 'stat', 'download', 'select']; +export const appendOnlyActions = readOnlyActions.concat('create'); +export const excludeUpdateActions = appendOnlyActions.concat('remove'); +export const genericActions = excludeUpdateActions.concat('update'); export type AbleAction = 'enable' | 'disable'; export type AbleState = 'enabled' | 'disabled'; diff --git a/src/checkers/index.ts b/src/checkers/index.ts new file mode 100644 index 0000000..8a8a517 --- /dev/null +++ b/src/checkers/index.ts @@ -0,0 +1,7 @@ +import { EntityDict } from '../base-app-domain'; +import { createModiRelatedCheckers } from '../store/modi'; +import { StorageSchema, EntityDict as BaseEntityDict, Context } from '../types'; + +export function createCheckers>(schema: StorageSchema){ + return createModiRelatedCheckers(schema); +} diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index 3de14ff..a2edcf4 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -27,6 +27,7 @@ const Schema: Record = {}; const OneToMany: Record> = {}; const ManyToOne: Record> = {}; @@ -285,7 +286,7 @@ const RESERVED_ACTION_NAMES = ['GenericAction', 'ParticularAction']; import { genericActions } from '../actions/action'; import { unIndexedTypes } from '../types/DataType'; import { initinctiveAttributes } from '../types/Entity'; -function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode, program: ts.Program) { +function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode, program: ts.Program, sourceFile: ts.SourceFile) { const actionTexts = genericActions.map( ele => ele ); @@ -343,8 +344,26 @@ function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode } ); - // 为每个action在schema中建立相应的state域(除了genericState) - // 放到actionDef的定义处去做。by Xc + pushStatementIntoActionAst(moduleName, + factory.createVariableStatement( + [factory.createModifier(ts.SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + factory.createIdentifier("actions"), + undefined, + undefined, + factory.createArrayLiteralExpression( + actionTexts.map( + ele => factory.createStringLiteral(ele) + ), + false + ) + )], + ts.NodeFlags.Const + ) + ), + sourceFile + ); } function getEntityImported(declaration: ts.ImportDeclaration, filename: string) { @@ -429,6 +448,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) { let hasRelationDef = false; let hasActionOrStateDef = false; let toModi = false; + let actionType = 'crud'; const enumStringAttrs: string[] = []; const states: string[] = []; const localEnumStringTypes: string[] = []; @@ -635,7 +655,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) { ), sourceFile! ); - dealWithActions(moduleName, filename, node.type, program); + dealWithActions(moduleName, filename, node.type, program, sourceFile!); } else if (node.name.text === 'Relation') { assert(!localeDef, `【${filename}】locale定义须在Relation之后`); @@ -685,6 +705,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) { [relationEntityName]: { schemaAttrs: relationSchemaAttrs, sourceFile, + actionType: 'crud', }, }); addRelationship(relationEntityName, 'User', 'user', true); @@ -916,12 +937,11 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) { indexes = declaration.initializer; } - else if (ts.isIdentifier(declaration.name) && declaration.name.text === 'locale') { + else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'LocaleDef') { // locale定义 const { type, initializer } = declaration; assert(ts.isObjectLiteralExpression(initializer!)); - assert(ts.isTypeReferenceNode(type!) && ts.isIdentifier(type.typeName!) && type.typeName.text === 'LocaleDef', 'locale定义的类型必须是LocaleDef'); const { properties } = initializer; assert(properties.length > 0, `${filename}至少需要有一种locale定义`); @@ -972,6 +992,10 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) { localeDef = initializer; } + else if (ts.isTypeReferenceNode(declaration.type!) && ts.isIdentifier(declaration.type.typeName) && declaration.type.typeName.text === 'ActionType') { + assert(ts.isStringLiteral(declaration.initializer!)); + actionType = declaration.initializer.text; + } else { throw new Error(`不能理解的定义内容${declaration.name.getText()}`); } @@ -983,11 +1007,15 @@ function analyzeEntity(filename: string, path: string, program: ts.Program) { if (!hasActionDef && hasActionOrStateDef) { throw new Error(`${filename}中有Action或State定义,但没有定义完整的Action类型`); } + if (hasActionDef && actionType !== 'crud') { + throw new Error(`${filename}中有Action定义,但却定义了actionType不是crud`); + } assert(schemaAttrs.length > 0); const schema = { schemaAttrs, sourceFile, toModi, + actionType, }; if (hasFulltextIndex) { assign(schema, { @@ -1510,17 +1538,17 @@ function constructFilter(statements: Array, entity: string) { if (ReversePointerRelations[entity]) { // 有反向指针,将反向指针关联的对象的Filter也注入 ReversePointerRelations[entity].forEach( - ele => - members.push( - factory.createPropertySignature( - undefined, - firstLetterLowerCase(ele), - undefined, - factory.createTypeReferenceNode( - createForeignRef(entity, ele, 'Filter') + ele => + members.push( + factory.createPropertySignature( + undefined, + firstLetterLowerCase(ele), + undefined, + factory.createTypeReferenceNode( + createForeignRef(entity, ele, 'Filter') + ) ) ) - ) ); } const eumUnionTypeNode: ts.TypeNode[] = ReversePointerRelations[entity] && ReversePointerRelations[entity].map( @@ -4922,7 +4950,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) { for (const entity in Schema) { const indexExpressions: ts.Expression[] = []; - const { sourceFile, fulltextIndex, indexes, toModi } = Schema[entity]; + const { sourceFile, fulltextIndex, indexes, toModi, actionType } = Schema[entity]; const statements: ts.Statement[] = [ factory.createImportDeclaration( undefined, @@ -4955,8 +4983,110 @@ function outputStorage(outputDir: string, printer: ts.Printer) { undefined ) ]; + switch (actionType) { + case 'readOnly': { + statements.push( + factory.createImportDeclaration( + undefined, + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([factory.createImportSpecifier( + false, + factory.createIdentifier("readOnlyActions"), + factory.createIdentifier("actions") + )]) + ), + factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), + undefined + ) + ); + break; + } + case 'appendOnly': { + statements.push( + factory.createImportDeclaration( + undefined, + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([factory.createImportSpecifier( + false, + factory.createIdentifier("appendOnlyActions"), + factory.createIdentifier("actions") + )]) + ), + factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), + undefined + ) + ); + break; + } + case 'excludeUpdate': { + statements.push( + factory.createImportDeclaration( + undefined, + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([factory.createImportSpecifier( + false, + factory.createIdentifier("excludeUpdateActions"), + factory.createIdentifier("actions") + )]) + ), + factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), + undefined + ) + ); + break; + } + default: { + if (ActionAsts[entity]) { + statements.push( + factory.createImportDeclaration( + undefined, + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([factory.createImportSpecifier( + false, + undefined, + factory.createIdentifier("actions") + )]) + ), + factory.createStringLiteral("./Action"), + undefined + ) + ); + } + else { + statements.push( + factory.createImportDeclaration( + undefined, + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([factory.createImportSpecifier( + false, + factory.createIdentifier("genericActions"), + factory.createIdentifier("actions") + )]) + ), + factory.createStringLiteral(ACTION_CONSTANT_IN_OAK_DOMAIN()), + undefined + ) + ); + } + } + } - const propertyAssignments: ts.PropertyAssignment[] = []; + const propertyAssignments: (ts.PropertyAssignment | ts.ShorthandPropertyAssignment)[] = []; const attributes = constructAttributes(entity); propertyAssignments.push( factory.createPropertyAssignment( @@ -4982,6 +5112,17 @@ function outputStorage(outputDir: string, printer: ts.Printer) { ); } + propertyAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("actionType"), + factory.createStringLiteral(actionType) + ), + factory.createShorthandPropertyAssignment( + factory.createIdentifier("actions"), + undefined + ) + ); + if (indexExpressions.length > 0) { propertyAssignments.push( factory.createPropertyAssignment( diff --git a/src/entities/ModiEntity.ts b/src/entities/ModiEntity.ts index 016619f..1eccac7 100644 --- a/src/entities/ModiEntity.ts +++ b/src/entities/ModiEntity.ts @@ -1,5 +1,5 @@ import { String } from '../types/DataType'; -import { EntityShape } from '../types/Entity'; +import { EntityShape, ActionType } from '../types/Entity'; import { LocaleDef } from '../types/Locale'; import { Schema as Modi } from './Modi'; @@ -9,6 +9,7 @@ export interface Schema extends EntityShape { entityId: String<64>; }; +const actionType: ActionType = 'appendOnly'; const locale: LocaleDef = { zh_CN: { diff --git a/src/entities/Oper.ts b/src/entities/Oper.ts index b2e0916..ec6be75 100644 --- a/src/entities/Oper.ts +++ b/src/entities/Oper.ts @@ -1,5 +1,5 @@ import { String, Int, Datetime, Image, Boolean, Text } from '../types/DataType'; -import { EntityShape } from '../types/Entity'; +import { EntityShape, ActionType } from '../types/Entity'; import { LocaleDef } from '../types/Locale'; import { Schema as User } from './User'; @@ -11,6 +11,8 @@ export interface Schema extends EntityShape { operator?: User; }; +const actionType: ActionType = 'appendOnly'; + const locale: LocaleDef = { zh_CN: { attr: { diff --git a/src/entities/OperEntity.ts b/src/entities/OperEntity.ts index 3311178..6ed607a 100644 --- a/src/entities/OperEntity.ts +++ b/src/entities/OperEntity.ts @@ -1,5 +1,5 @@ import { String } from '../types/DataType'; -import { EntityShape } from '../types/Entity'; +import { EntityShape, ActionType } from '../types/Entity'; import { LocaleDef } from '../types/Locale'; import { Schema as Oper } from './Oper'; @@ -9,6 +9,7 @@ export interface Schema extends EntityShape { entityId: String<64>; }; +const actionType: ActionType = 'appendOnly'; const locale: LocaleDef = { zh_CN: { diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index 38e1f2b..221e2e5 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -633,7 +633,7 @@ export abstract class CascadeStore, entity: string, @@ -17,4 +22,93 @@ export function createOperationsFromModies(modies: Modi[]): Array<{ } } ); +} + +export async function applyModis>(filter: Filter, context: Cxt) { + return context.rowStore.operate('modi', { + id: await generateNewId(), + action: 'apply', + data: {}, + filter, + sorter: [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'asc', + } + ] + }, context, { + dontCollect: true, + blockTrigger: true, + }); +} + +export async function abandonModis>(filter: Filter, context: Cxt) { + return context.rowStore.operate('modi', { + id: await generateNewId(), + action: 'abadon', + data: {}, + filter, + sorter: [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'asc', + } + ] + }, context, { + dontCollect: true, + blockTrigger: true, + }); +} + +export function createModiRelatedCheckers>(schema: StorageSchema) { + const checkers:Checker[] = []; + + for (const entity in schema) { + const { actionType, actions } = schema[entity]; + if (['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity) || ['readOnly', 'appendOnly'].includes(actionType)) { + continue; + } + const restActions = difference(actions, appendOnlyActions); + checkers.push({ + entity, + action: restActions as any, + type: 'row', + checker: async ({ operation }, context) => { + const { filter } = operation; + const filter2 = { + modi: { + iState: 'active', + }, + }; + if (filter) { + Object.assign(filter2, { + [entity]: filter + }); + } + else { + Object.assign(filter2, { + entity, + }); + } + const count = await context.rowStore.count( + 'modiEntity', + { + filter: filter2 as any, + }, + context, + {} + ); + if (count > 0) { + throw new OakRowLockedException(); + } + return 0; + }, + } as UpdateChecker) + } + + return checkers; } \ No newline at end of file diff --git a/src/triggers/modi.ts b/src/triggers/modi.ts index b79dcaa..3f114b9 100644 --- a/src/triggers/modi.ts +++ b/src/triggers/modi.ts @@ -28,7 +28,9 @@ const triggers: Trigger>[] = [ action, data, filter: filter as any, - }, context, option); + }, context, Object.assign({}, option, { + blockTrigger: true, + })); } return modies.length; diff --git a/src/types/Entity.ts b/src/types/Entity.ts index 8f4af5d..997e182 100644 --- a/src/types/Entity.ts +++ b/src/types/Entity.ts @@ -17,7 +17,7 @@ export type Filter = export type SelectOption = { dontCollect?: boolean; - ignoreTrigger?: true; + blockTrigger?: true; obscure?: boolean; // 如果为置为true,则在filter过程中因数据不完整而不能判断为真的时候都假设为真(前端缓存专用) forUpdate?: true; includedDeleted?: true; // 是否包含删除行的信息 @@ -25,6 +25,7 @@ export type SelectOption = { }; export type OperateOption = { + blockTrigger?: true; dontCollect?: boolean; dontCreateOper?: boolean; allowExists?: boolean; // 插入时允许已经存在唯一键值的行了,即insert / update逻辑 @@ -47,7 +48,6 @@ export type Operation; export type Selection> = { result: Array>; } + +export type ActionType = 'readOnly' | 'appendOnly' | 'excludeUpdate' | 'crud'; // 只读型、只插入型、没有更新型、所有型 diff --git a/src/types/Exception.ts b/src/types/Exception.ts index ba05995..b8be11d 100644 --- a/src/types/Exception.ts +++ b/src/types/Exception.ts @@ -108,6 +108,15 @@ export class OakUnloggedInException extends OakUserException { } }; + +/** + * 用户未登录抛的异常 + */ + export class OakRowLockedException extends OakUserException { + constructor(message?: string) { + super(message || '该行数据正在被更新中,请稍后再试'); + } +}; /** * 要插入行时,发现已经有相同的行数据 */ @@ -162,6 +171,9 @@ export function makeException(data: { case OakCongruentRowExists.name: { return new OakCongruentRowExists(data.data, data.message); } + case OakRowLockedException.name: { + return new OakRowLockedException(data.message); + } default: return; } diff --git a/src/types/Storage.ts b/src/types/Storage.ts index e2c2ea1..0066cfb 100644 --- a/src/types/Storage.ts +++ b/src/types/Storage.ts @@ -1,3 +1,4 @@ +import { ActionType } from '.'; import { EntityDict, EntityShape, InstinctiveAttributes } from './Entity'; import { DataType, DataTypeParams } from './schema/DataTypes'; @@ -52,7 +53,9 @@ export interface StorageDesc { uniqueConstraints?: UniqConstraint[]; indexes?: Index[]; config?: EntityConfig; - toModi?: true, // 标识一下是否关联在modi上 + toModi?: true; // 标识一下是否关联在modi上 + actions: string[]; + actionType: ActionType; // view 相关 view?: true; } diff --git a/src/utils/lodash.ts b/src/utils/lodash.ts index e2fe466..52a809c 100644 --- a/src/utils/lodash.ts +++ b/src/utils/lodash.ts @@ -14,6 +14,7 @@ import cloneDeep from 'lodash/cloneDeep'; import pick from 'lodash/pick'; import isEqual from 'lodash/isEqual'; import union from 'lodash/union'; +import difference from 'lodash/difference'; export { unset, @@ -28,4 +29,5 @@ export { pick, isEqual, union, + difference, };