From ef6175313ff5939c6e5e6707f955bfdab5fb2fa9 Mon Sep 17 00:00:00 2001 From: "Xc@centOs" Date: Tue, 18 Jul 2023 18:54:26 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=82=E9=85=8D=E4=BA=86domain=E7=9A=84?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=80=A7=E6=9B=B4=E6=96=B0,=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BA=86=E4=B8=8D=E5=B0=91=E4=BB=A3=E7=A0=81=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/cacheStore/CacheStore.d.ts | 13 ++- lib/cacheStore/CacheStore.js | 36 ++++--- lib/cacheStore/SyncTriggerExecutor.d.ts | 11 ++ lib/cacheStore/SyncTriggerExecutor.js | 123 ++++++++++++++++++++++ lib/debugStore/index.js | 6 +- lib/features/cache.d.ts | 2 +- lib/features/cache.js | 2 +- lib/features/contextMenuFactory.d.ts | 4 +- lib/features/contextMenuFactory.js | 12 ++- lib/features/index.js | 4 +- lib/features/relationAuth.d.ts | 5 +- lib/features/relationAuth.js | 25 ++++- lib/features/runningTree.d.ts | 2 +- lib/initialize-dev.d.ts | 2 +- lib/initialize-dev.js | 17 ++-- lib/initialize-prod.js | 9 +- lib/page.common.js | 15 ++- lib/page.mp.js | 7 ++ lib/page.web.js | 7 ++ src/cacheStore/CacheStore.ts | 52 ++++++---- src/cacheStore/CheckerExecutor.ts | 125 ----------------------- src/cacheStore/SyncTriggerExecutor.ts | 129 ++++++++++++++++++++++++ src/debugStore/index.ts | 11 +- src/features/cache.ts | 3 +- src/features/contextMenuFactory.ts | 15 ++- src/features/index.ts | 4 +- src/features/relationAuth.ts | 24 ++++- src/initialize-dev.ts | 24 ++--- src/initialize-prod.ts | 18 ++-- src/page.common.ts | 9 +- src/page.mp.ts | 16 +++ src/page.web.tsx | 16 +++ 32 files changed, 495 insertions(+), 253 deletions(-) create mode 100644 lib/cacheStore/SyncTriggerExecutor.d.ts create mode 100644 lib/cacheStore/SyncTriggerExecutor.js delete mode 100644 src/cacheStore/CheckerExecutor.ts create mode 100644 src/cacheStore/SyncTriggerExecutor.ts diff --git a/lib/cacheStore/CacheStore.d.ts b/lib/cacheStore/CacheStore.d.ts index 27d1c7eb..3b5ec0d6 100644 --- a/lib/cacheStore/CacheStore.d.ts +++ b/lib/cacheStore/CacheStore.d.ts @@ -1,22 +1,24 @@ -import { AggregationResult, EntityDict, OperateOption, OperationResult, OpRecord, SelectOption } from 'oak-domain/lib/types/Entity'; +import { AggregationResult, EntityDict, OperationResult, OpRecord, SelectOption } from 'oak-domain/lib/types/Entity'; import { StorageSchema } from "oak-domain/lib/types/Storage"; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { Checker, CheckerType, TxnOption } from 'oak-domain/lib/types'; import { TreeStore, TreeStoreOperateOption } from 'oak-memory-tree-store'; import { SyncContext, SyncRowStore } from 'oak-domain/lib/store/SyncRowStore'; +interface CachStoreOperation extends TreeStoreOperateOption { + checkerTypes?: CheckerType[]; +} export declare class CacheStore> extends TreeStore implements SyncRowStore> { - private checkerExecutor; + private triggerExecutor; private getFullDataFn?; private resetInitialDataFn?; constructor(storageSchema: StorageSchema, getFullDataFn?: () => any, resetInitialDataFn?: () => void); aggregate(entity: T, aggregation: ED[T]['Aggregation'], context: SyncContext, option: OP): AggregationResult; - protected cascadeUpdate(entity: T, operation: ED[T]['Operation'], context: SyncContext, option: OP): OperationResult; - operate(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult; + protected cascadeUpdate(entity: T, operation: ED[T]['Operation'], context: SyncContext, option: OP): OperationResult; + operate(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult; sync>(opRecords: Array>, context: Cxt): void; check(entity: T, operation: Omit, context: Cxt, checkerTypes?: CheckerType[]): void; select>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial[]; registerChecker(checker: Checker): void; - registerGeneralChecker(type: CheckerType, fn: (entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) => void): void; /** * 这个函数是在debug下用来获取debugStore的数据,release下不能使用 * @returns @@ -32,3 +34,4 @@ export declare class CacheStore(trigger: Trigger) { + this.triggerExecutor.registerTrigger(trigger); + } */ /** * 这个函数是在debug下用来获取debugStore的数据,release下不能使用 * @returns diff --git a/lib/cacheStore/SyncTriggerExecutor.d.ts b/lib/cacheStore/SyncTriggerExecutor.d.ts new file mode 100644 index 00000000..d93016d7 --- /dev/null +++ b/lib/cacheStore/SyncTriggerExecutor.d.ts @@ -0,0 +1,11 @@ +import { EntityDict } from 'oak-domain/lib/types/Entity'; +import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; +import { Checker, CheckerType } from 'oak-domain/lib/types'; +import { SyncContext } from 'oak-domain/lib/store/SyncRowStore'; +export default class SyncTriggerExecutor> { + static All_Checker_Types: CheckerType[]; + private checkerMap; + private addToCheckerMap; + registerChecker(checker: Checker): void; + check(entity: T, operation: Omit, context: Cxt, when?: 'before' | 'after', checkerTypes?: CheckerType[]): void; +} diff --git a/lib/cacheStore/SyncTriggerExecutor.js b/lib/cacheStore/SyncTriggerExecutor.js new file mode 100644 index 00000000..055b52cd --- /dev/null +++ b/lib/cacheStore/SyncTriggerExecutor.js @@ -0,0 +1,123 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +// 简化版的对checker的同步检查 +var assert_1 = tslib_1.__importDefault(require("assert")); +var types_1 = require("oak-domain/lib/types"); +var checker_1 = require("oak-domain/lib/store/checker"); +var filter_1 = require("oak-domain/lib/store/filter"); +var SyncTriggerExecutor = /** @class */ (function () { + function SyncTriggerExecutor() { + this.checkerMap = {}; + } + SyncTriggerExecutor.prototype.addToCheckerMap = function (action, entity, priority, when, fn, type, filter) { + var _a, _b, _c; + if (this.checkerMap[entity] && this.checkerMap[entity][action]) { + var iter = 0; + var checkers = this.checkerMap[entity][action]; + for (; iter < checkers.length; iter++) { + if (priority <= checkers[iter].priority) { + break; + } + } + checkers.splice(iter, 0, { + type: type, + priority: priority, + fn: fn, + when: when, + filter: filter, + }); + } + else if (this.checkerMap[entity]) { + Object.assign(this.checkerMap[entity], (_a = {}, + _a[action] = [{ + type: type, + priority: priority, + fn: fn, + when: when, + filter: filter, + }], + _a)); + } + else { + Object.assign(this.checkerMap, (_b = {}, + _b[entity] = (_c = {}, + _c[action] = [{ + type: type, + priority: priority, + fn: fn, + when: when, + filter: filter, + }], + _c), + _b)); + } + }; + SyncTriggerExecutor.prototype.registerChecker = function (checker) { + var _this = this; + var entity = checker.entity, action = checker.action, priority = checker.priority, type = checker.type, conditionalFilter = checker.conditionalFilter; + var _a = (0, checker_1.translateCheckerInSyncContext)(checker), fn = _a.fn, when = _a.when; + if (action instanceof Array) { + action.forEach(function (a) { return _this.addToCheckerMap(a, entity, priority || types_1.CHECKER_PRIORITY_MAP[type], when, fn, type, conditionalFilter); }); + } + else { + this.addToCheckerMap(action, entity, priority, when, fn, type, conditionalFilter); + } + }; + /* registerTrigger(trigger: Trigger) { + const { + action, + entity, + priority, + fn, + when, + filter, + } = trigger as UpdateTrigger; + + if (when === 'commit') { + return; + } + if (action instanceof Array) { + action.forEach( + (a) => this.addToCheckerMap(a, entity, priority || TRIGGER_DEFAULT_PRIORITY, when, fn as any, undefined, filter) + ); + } + else { + this.addToCheckerMap(action ,entity, priority || TRIGGER_DEFAULT_PRIORITY, when, fn as any, undefined, filter); + } + } */ + SyncTriggerExecutor.prototype.check = function (entity, operation, context, when, checkerTypes) { + var e_1, _a; + var action = operation.action; + var checkers = this.checkerMap[entity] && this.checkerMap[entity][action]; + if (checkers) { + var checkers2 = checkers.filter(function (ele) { return (!checkerTypes || checkerTypes.includes(ele.type)) && (!when || ele.when === when); }); + try { + for (var checkers2_1 = tslib_1.__values(checkers2), checkers2_1_1 = checkers2_1.next(); !checkers2_1_1.done; checkers2_1_1 = checkers2_1.next()) { + var checker = checkers2_1_1.value; + var filter = checker.filter; + if (filter) { + var filterr = typeof filter === 'function' ? filter(operation, context, {}) : filter; + (0, assert_1.default)(!(filter instanceof Promise), "\u5BF9".concat(entity, "\u7684\u52A8\u4F5C").concat(action, "\u4E0A\u5B9A\u4E49\u7684checker\uFF0C\u5176filter\u8FD4\u56DE\u4E86Promise\uFF0C\u8BF7\u6CE8\u610F\u5C06\u540C\u6B65\u548C\u5F02\u6B65\u7684\u8FD4\u56DE\u533A\u5206\u5BF9\u5F85")); + var isRepel = (0, filter_1.checkFilterRepel)(entity, context, filterr, operation.filter, true); + (0, assert_1.default)(typeof isRepel === 'boolean'); + if (isRepel) { + continue; + } + } + checker.fn(operation, context, {}); + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (checkers2_1_1 && !checkers2_1_1.done && (_a = checkers2_1.return)) _a.call(checkers2_1); + } + finally { if (e_1) throw e_1.error; } + } + } + }; + SyncTriggerExecutor.All_Checker_Types = ['data', 'logical', 'logicalRelation', 'relation', 'row']; + return SyncTriggerExecutor; +}()); +exports.default = SyncTriggerExecutor; diff --git a/lib/debugStore/index.js b/lib/debugStore/index.js index 482b2447..2dcd93c9 100644 --- a/lib/debugStore/index.js +++ b/lib/debugStore/index.js @@ -5,7 +5,6 @@ var tslib_1 = require("tslib"); var node_schedule_1 = require("node-schedule"); var constant_1 = require("../constant/constant"); var DebugStore_1 = require("./DebugStore"); -var actionDef_1 = require("oak-domain/lib/store/actionDef"); var assert_1 = require("oak-domain/lib/utils/assert"); var uuid_1 = require("oak-domain/lib/utils/uuid"); function initDataInStore(store, initialData, stat) { @@ -387,9 +386,6 @@ function createDebugStore(storageSchema, contextBuilder, triggers, checkers, wat triggers.forEach(function (ele) { return store.registerTrigger(ele); }); checkers.forEach(function (ele) { return store.registerChecker(ele); }); (0, assert_1.assert)(actionDict); - var _a = (0, actionDef_1.analyzeActionDefDict)(storageSchema, actionDict), adTriggers = _a.triggers, adCheckers = _a.checkers, adWatchers = _a.watchers; - adTriggers.forEach(function (ele) { return store.registerTrigger(ele); }); - adCheckers.forEach(function (ele) { return store.registerChecker(ele); }); // 如果没有物化数据则使用initialData初始化debugStore var data = getMaterializedData(); if (!data) { @@ -411,7 +407,7 @@ function createDebugStore(storageSchema, contextBuilder, triggers, checkers, wat materializeData(data, stat); }, 10000); // 启动watcher - initializeWatchers(store, contextBuilder, watchers.concat(adWatchers)); + initializeWatchers(store, contextBuilder, watchers); // 启动timer if (timers) { initializeTimers(store, contextBuilder, timers); diff --git a/lib/features/cache.d.ts b/lib/features/cache.d.ts index fe720422..d76eef75 100644 --- a/lib/features/cache.d.ts +++ b/lib/features/cache.d.ts @@ -46,7 +46,7 @@ export declare class Cache, context: FrontCxt): void; private getInner; get(entity: T, selection: ED[T]['Selection'], context?: FrontCxt, allowMiss?: boolean): Partial[]; - judgeRelation(entity: keyof ED, attr: string): string | 0 | 1 | 2 | string[]; + judgeRelation(entity: keyof ED, attr: string): string | 0 | 1 | string[] | 2; bindOnSync(callback: (opRecords: OpRecord[]) => void): void; unbindOnSync(callback: (opRecords: OpRecord[]) => void): void; getCachedData(): { [T in keyof ED]?: ED[T]["OpSchema"][] | undefined; }; diff --git a/lib/features/cache.js b/lib/features/cache.js index a27f060f..27f11d0c 100644 --- a/lib/features/cache.js +++ b/lib/features/cache.js @@ -213,7 +213,7 @@ var Cache = /** @class */ (function (_super) { var operation = { action: action, filter: filter, - data: data, + data: data }; try { this.cacheStore.check(entity, operation, context, checkerTypes); diff --git a/lib/features/contextMenuFactory.d.ts b/lib/features/contextMenuFactory.d.ts index 3051e7b6..81aca49f 100644 --- a/lib/features/contextMenuFactory.d.ts +++ b/lib/features/contextMenuFactory.d.ts @@ -5,6 +5,7 @@ import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore'; import { SyncContext } from 'oak-domain/lib/store/SyncRowStore'; import { Cache } from './cache'; import { Feature } from '../types/Feature'; +import { RelationAuth } from './relationAuth'; interface IMenu { name: string; entity: T; @@ -18,8 +19,9 @@ export declare class ContextMenuFactory; menuWrappers?: IMenuWrapper[]; cascadePathGraph: AuthCascadePath[]; + relationAuth: RelationAuth; private makeMenuWrappers; - constructor(cache: Cache, cascadePathGraph: AuthCascadePath[]); + constructor(cache: Cache, relationAuth: RelationAuth, cascadePathGraph: AuthCascadePath[]); setMenus(menus: IMenu[]): void; getMenusByContext>(entity: keyof ED, entityId: string): OMenu[]; } diff --git a/lib/features/contextMenuFactory.js b/lib/features/contextMenuFactory.js index 80af4de6..75823565 100644 --- a/lib/features/contextMenuFactory.js +++ b/lib/features/contextMenuFactory.js @@ -10,10 +10,11 @@ var relation_1 = require("oak-domain/lib/store/relation"); ; var ContextMenuFactory = /** @class */ (function (_super) { tslib_1.__extends(ContextMenuFactory, _super); - function ContextMenuFactory(cache, cascadePathGraph) { + function ContextMenuFactory(cache, relationAuth, cascadePathGraph) { var _this = _super.call(this) || this; _this.cache = cache; _this.cascadePathGraph = cascadePathGraph; + _this.relationAuth = relationAuth; return _this; } ContextMenuFactory.prototype.makeMenuWrappers = function (menus) { @@ -95,7 +96,14 @@ var ContextMenuFactory = /** @class */ (function (_super) { var filters = filtersMaker(entity, entityId); if (filters.length > 0) { // 这里应该是or关系,paths表达的路径中只要有一条满足就可能满足 - var allows = filters.map(function (filter) { return _this.cache.checkOperation(destEntity, action, undefined, filter, ['logical', 'relation', 'logicalRelation', 'row']); }); + var allows = filters.map(function (filter) { + // relationAuth和其它的checker现在分开判断 + return _this.relationAuth.checkRelation(destEntity, { + action: action, + data: undefined, + filter: filter, + }) && _this.cache.checkOperation(destEntity, action, undefined, filter, ['logical', 'relation', 'logicalRelation', 'row']); + }); if (allows.indexOf(true) >= 0) { return true; } diff --git a/lib/features/index.js b/lib/features/index.js index b306864c..4d5b18be 100644 --- a/lib/features/index.js +++ b/lib/features/index.js @@ -18,7 +18,7 @@ var geo_1 = require("./geo"); function initialize(aspectWrapper, storageSchema, contextBuilder, store, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, colorDict, makeBridgeUrlFn) { var cache = new cache_1.Cache(aspectWrapper, contextBuilder, store); var location = new location_1.Location(); - var relationAuth = new relationAuth_1.RelationAuth(aspectWrapper, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities); + var relationAuth = new relationAuth_1.RelationAuth(aspectWrapper, contextBuilder, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities); var runningTree = new runningTree_1.RunningTree(cache, storageSchema, relationAuth); var locales = new locales_1.Locales(aspectWrapper, makeBridgeUrlFn); var geo = new geo_1.Geo(aspectWrapper); @@ -29,7 +29,7 @@ function initialize(aspectWrapper, storageSchema, contextBuilder, store, actionC var navigator = new navigator_1.Navigator(); var port = new port_1.Port(aspectWrapper); var style = new style_1.Style(colorDict); - var contextMenuFactory = new contextMenuFactory_1.ContextMenuFactory(cache, actionCascadePathGraph); + var contextMenuFactory = new contextMenuFactory_1.ContextMenuFactory(cache, relationAuth, actionCascadePathGraph); return { cache: cache, location: location, diff --git a/lib/features/relationAuth.d.ts b/lib/features/relationAuth.d.ts index bd695785..76add3bb 100644 --- a/lib/features/relationAuth.d.ts +++ b/lib/features/relationAuth.d.ts @@ -7,6 +7,7 @@ import { SyncContext } from 'oak-domain/lib/store/SyncRowStore'; import { Cache } from './cache'; export declare class RelationAuth, FrontCxt extends SyncContext, AD extends CommonAspectDict & Record>> extends Feature { private cache; + private contextBuilder; private aspectWrapper; private actionCascadePathGraph; private actionCascadePathMap; @@ -15,7 +16,7 @@ export declare class RelationAuth, cache: Cache, actionCascadePathGraph: AuthCascadePath[], relationCascadePathGraph: AuthCascadePath[], authDeduceRelationMap: AuthDeduceRelationMap, selectFreeEntities: (keyof ED)[]); + constructor(aspectWrapper: AspectWrapper, contextBuilder: () => FrontCxt, cache: Cache, actionCascadePathGraph: AuthCascadePath[], relationCascadePathGraph: AuthCascadePath[], authDeduceRelationMap: AuthDeduceRelationMap, selectFreeEntities: (keyof ED)[]); private judgeRelation; getHasRelationEntities(): string[]; getDeduceRelationAttribute(entity: keyof ED): string | undefined; @@ -40,6 +41,6 @@ export declare class RelationAuth[]; getCascadeRelationAuthsBySource(entity: keyof ED): AuthCascadePath[]; getCascadeRelationAuths(entity: keyof ED, ir: boolean): AuthCascadePath[]; - checkRelation(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: FrontCxt): void; + checkRelation(entity: T, operation: Omit): boolean; getRelationIdByName(entity: keyof ED, name: string, entityId?: string): Promise; } diff --git a/lib/features/relationAuth.js b/lib/features/relationAuth.js index b4996f1a..3c9c43dc 100644 --- a/lib/features/relationAuth.js +++ b/lib/features/relationAuth.js @@ -2,16 +2,18 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.RelationAuth = void 0; var tslib_1 = require("tslib"); +var types_1 = require("oak-domain/lib/types"); var Feature_1 = require("../types/Feature"); var lodash_1 = require("oak-domain/lib/utils/lodash"); var RelationAuth_1 = require("oak-domain/lib/store/RelationAuth"); var relation_1 = require("oak-domain/lib/store/relation"); var RelationAuth = /** @class */ (function (_super) { tslib_1.__extends(RelationAuth, _super); - function RelationAuth(aspectWrapper, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities) { + function RelationAuth(aspectWrapper, contextBuilder, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities) { var _this = _super.call(this) || this; - _this.aspectWrapper = aspectWrapper, - _this.cache = cache; + _this.aspectWrapper = aspectWrapper; + _this.contextBuilder = contextBuilder; + _this.cache = cache; _this.actionCascadePathGraph = actionCascadePathGraph; _this.relationCascadePathGraph = relationCascadePathGraph; _this.actionCascadePathMap = {}; @@ -163,8 +165,21 @@ var RelationAuth = /** @class */ (function (_super) { var relationAuths = this.relationCascadePathGraph.filter(function (ele) { return ele[0] === entity && ir === ele[3]; }); return relationAuths; }; - RelationAuth.prototype.checkRelation = function (entity, operation, context) { - this.baseRelationAuth.checkRelationSync(entity, operation, context); + RelationAuth.prototype.checkRelation = function (entity, operation) { + var context = this.contextBuilder(); + context.begin(); + try { + this.baseRelationAuth.checkRelationSync(entity, operation, context); + } + catch (err) { + context.rollback(); + if (!(err instanceof types_1.OakUserException)) { + throw err; + } + return false; + } + context.rollback(); + return true; }; RelationAuth.prototype.getRelationIdByName = function (entity, name, entityId) { return tslib_1.__awaiter(this, void 0, void 0, function () { diff --git a/lib/features/runningTree.d.ts b/lib/features/runningTree.d.ts index aa568451..7e45d06d 100644 --- a/lib/features/runningTree.d.ts +++ b/lib/features/runningTree.d.ts @@ -50,7 +50,7 @@ declare abstract class Node | ListNode | VirtualNode | undefined; protected getProjection(context?: FrontCxt): ED[T]['Selection']['data'] | undefined; setProjection(projection: ED[T]['Selection']['data']): void; - protected judgeRelation(attr: string): string | 0 | 1 | 2 | string[]; + protected judgeRelation(attr: string): string | 0 | 1 | string[] | 2; protected contains(filter: ED[T]['Selection']['filter'], conditionalFilter: ED[T]['Selection']['filter']): boolean; protected repel(filter1: ED[T]['Selection']['filter'], filter2: ED[T]['Selection']['filter']): boolean; } diff --git a/lib/initialize-dev.d.ts b/lib/initialize-dev.d.ts index d4847382..7f55da07 100644 --- a/lib/initialize-dev.d.ts +++ b/lib/initialize-dev.d.ts @@ -20,7 +20,7 @@ import { InitializeOptions } from './types/Initialize'; * @param actionDict * @returns */ -export declare function initialize, FrontCxt extends SyncContext, AD extends Record>>(storageSchema: StorageSchema, frontendContextBuilder: () => (store: CacheStore) => FrontCxt, backendContextBuilder: (contextStr?: string) => (store: DebugStore) => Promise, aspectDict: AD, triggers: Array>, checkers: Array>, watchers: Array>, timers: Array>, startRoutines: Array>, initialData: { +export declare function initialize, FrontCxt extends SyncContext, AD extends Record>>(storageSchema: StorageSchema, frontendContextBuilder: () => (store: CacheStore) => FrontCxt, backendContextBuilder: (contextStr?: string) => (store: DebugStore) => Promise, aspectDict: AD, triggers: Array>, checkers: Array>, watchers: Array>, timers: Array>, startRoutines: Array>, initialData: { [T in keyof ED]?: Array; }, option: InitializeOptions): { features: import("./features").BasicFeatures & AD>; diff --git a/lib/initialize-dev.js b/lib/initialize-dev.js index 3ec6e14d..d5f945cd 100644 --- a/lib/initialize-dev.js +++ b/lib/initialize-dev.js @@ -2,13 +2,11 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.initialize = void 0; var tslib_1 = require("tslib"); -var index_1 = require("oak-domain/lib/checkers/index"); -var index_2 = require("oak-domain/lib/triggers/index"); +var actionDef_1 = require("oak-domain/lib/store/actionDef"); var debugStore_1 = require("./debugStore"); var features_1 = require("./features"); var lodash_1 = require("oak-domain/lib/utils/lodash"); var oak_common_aspect_1 = tslib_1.__importDefault(require("oak-common-aspect")); -var actionDef_1 = require("oak-domain/lib/store/actionDef"); var oak_common_aspect_2 = require("oak-common-aspect"); var CacheStore_1 = require("./cacheStore/CacheStore"); /** @@ -32,9 +30,11 @@ function initialize(storageSchema, frontendContextBuilder, backendContextBuilder throw new Error("\u7528\u6237\u5B9A\u4E49\u7684aspect\u4E2D\u4E0D\u80FD\u548C\u7CFB\u7EDFaspect\u540C\u540D\uFF1A\u300C".concat(intersected.join(','), "\u300D")); } var aspectDict2 = Object.assign({}, aspectDict, oak_common_aspect_1.default); - var checkers2 = (checkers).concat((0, index_1.createDynamicCheckers)(storageSchema)); - var triggers2 = (0, index_2.createDynamicTriggers)(storageSchema).concat(triggers); - var debugStore = (0, debugStore_1.createDebugStore)(storageSchema, backendContextBuilder, triggers2, checkers2, watchers, timers, startRoutines, initialData, actionDict, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities); + var _a = (0, actionDef_1.makeIntrinsicCTWs)(storageSchema, actionDict), intCheckers = _a.checkers, intTriggers = _a.triggers, intWatchers = _a.watchers; + var checkers2 = checkers.concat(intCheckers); + var triggers2 = triggers.concat(intTriggers); + var watchers2 = watchers.concat(intWatchers); + var debugStore = (0, debugStore_1.createDebugStore)(storageSchema, backendContextBuilder, triggers2, checkers2, watchers2, timers, startRoutines, initialData, actionDict, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities); var cacheStore = new CacheStore_1.CacheStore(storageSchema, function () { return debugStore.getCurrentData(); }, function () { return (0, debugStore_1.clearMaterializedData)(); }); var wrapper = { exec: function (name, params) { return tslib_1.__awaiter(_this, void 0, void 0, function () { @@ -77,11 +77,6 @@ function initialize(storageSchema, frontendContextBuilder, backendContextBuilder }; var features = (0, features_1.initialize)(wrapper, storageSchema, function () { return frontendContextBuilder()(cacheStore); }, cacheStore, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, colorDict); checkers2.forEach(function (checker) { return cacheStore.registerChecker(checker); }); - if (actionDict) { - var adCheckers = (0, actionDef_1.analyzeActionDefDict)(storageSchema, actionDict).checkers; - adCheckers.forEach(function (checker) { return cacheStore.registerChecker(checker); }); - } - cacheStore.registerGeneralChecker('relation', function (entity, operation, context) { return features.relationAuth.checkRelation(entity, operation, context); }); (0, oak_common_aspect_2.registerPorts)(importations || [], exportations || []); return { features: features, diff --git a/lib/initialize-prod.js b/lib/initialize-prod.js index 1c08793a..b40d3192 100644 --- a/lib/initialize-prod.js +++ b/lib/initialize-prod.js @@ -5,7 +5,6 @@ var tslib_1 = require("tslib"); var features_1 = require("./features"); var actionDef_1 = require("oak-domain/lib/store/actionDef"); var CacheStore_1 = require("./cacheStore/CacheStore"); -var checkers_1 = require("oak-domain/lib/checkers"); /** * @param storageSchema * @param createFeatures @@ -22,7 +21,8 @@ var checkers_1 = require("oak-domain/lib/checkers"); function initialize(storageSchema, frontendContextBuilder, connector, checkers, option) { var _this = this; var actionCascadePathGraph = option.actionCascadePathGraph, relationCascadePathGraph = option.relationCascadePathGraph, authDeduceRelationMap = option.authDeduceRelationMap, actionDict = option.actionDict, selectFreeEntities = option.selectFreeEntities, colorDict = option.colorDict; - var checkers2 = (checkers || []).concat((0, checkers_1.createDynamicCheckers)(storageSchema)); + var intCheckers = (0, actionDef_1.makeIntrinsicCTWs)(storageSchema, actionDict).checkers; + var checkers2 = checkers.concat(intCheckers); var cacheStore = new CacheStore_1.CacheStore(storageSchema); var wrapper = { exec: function (name, params) { return tslib_1.__awaiter(_this, void 0, void 0, function () { @@ -45,11 +45,6 @@ function initialize(storageSchema, frontendContextBuilder, connector, checkers, }; var features = (0, features_1.initialize)(wrapper, storageSchema, function () { return frontendContextBuilder()(cacheStore); }, cacheStore, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, colorDict, function (url, headers) { return connector.makeBridgeUrl(url, headers); }); checkers2.forEach(function (checker) { return cacheStore.registerChecker(checker); }); - if (actionDict) { - var adCheckers = (0, actionDef_1.analyzeActionDefDict)(storageSchema, actionDict).checkers; - adCheckers.forEach(function (checker) { return cacheStore.registerChecker(checker); }); - } - cacheStore.registerGeneralChecker('relation', function (entity, operation, context) { return features.relationAuth.checkRelation(entity, operation, context); }); return { features: features, }; diff --git a/lib/page.common.js b/lib/page.common.js index 8ffe3ed2..a1df5bf7 100644 --- a/lib/page.common.js +++ b/lib/page.common.js @@ -7,6 +7,7 @@ var lodash_1 = require("oak-domain/lib/utils/lodash"); var relation_1 = require("oak-domain/lib/store/relation"); var filter_1 = require("oak-domain/lib/store/filter"); var runningTree_1 = require("./features/runningTree"); +var uuid_1 = require("oak-domain/lib/utils/uuid"); function onPathSet(option) { return tslib_1.__awaiter(this, void 0, void 0, function () { var _a, props, state, _b, oakPath, oakProjection, oakFilters, oakSorters, oakId, entity, path, projection, isList, filters, sorters, pagination, features, oakPath2, entity2_1, filters2, oakFilters2, _loop_1, filters_1, filters_1_1, ele, proj, sorters2, oakSorters2, _loop_2, sorters_1, sorters_1_1, ele, actions_1, cascadeActions_1; @@ -172,7 +173,7 @@ function checkActionsAndCascadeEntities(rows, option) { var filter = this_1.features.runningTree.getIntrinsticFilters(this_1.state.oakFullpath); if (action === 'create' || typeof action === 'object' && action.action === 'create') { // 创建对象的判定不落在具体行上,但要考虑list上外键相关属性的限制 - var data = typeof action === 'object' && (0, lodash_1.cloneDeep)(action.data); + var data = typeof action === 'object' && Object.assign((0, lodash_1.cloneDeep)(action.data) || {}, { id: (0, uuid_1.generateNewId)() }); if (this_1.checkOperation(this_1.state.oakEntity, 'create', data, filter, checkTypes)) { legalActions.push(action); } @@ -295,9 +296,11 @@ function checkActionsAndCascadeEntities(rows, option) { if (action === 'create' || typeof action === 'object' && action.action === 'create') { rows.forEach(function (row) { var _a; - var intrinsticData = rel_1[1] ? (_a = {}, + var intrinsticData = rel_1[1] ? (_a = { + id: (0, uuid_1.generateNewId)() + }, _a[rel_1[1]] = row.id, - _a) : { entity: _this.state.oakEntity, entityId: row.id }; + _a) : { id: (0, uuid_1.generateNewId)(), entity: _this.state.oakEntity, entityId: row.id }; if (typeof action === 'object') { Object.assign(intrinsticData, action.data); } @@ -339,9 +342,11 @@ function checkActionsAndCascadeEntities(rows, option) { } else { if (action === 'create' || typeof action === 'object' && action.action === 'create') { - var intrinsticData = rel_1[1] ? (_k = {}, + var intrinsticData = rel_1[1] ? (_k = { + id: (0, uuid_1.generateNewId)() + }, _k[rel_1[1]] = rows.id, - _k) : { entity: this_2.state.oakEntity, entityId: rows.id }; + _k) : { id: (0, uuid_1.generateNewId)(), entity: this_2.state.oakEntity, entityId: rows.id }; if (typeof action === 'object') { Object.assign(intrinsticData, action.data); } diff --git a/lib/page.mp.js b/lib/page.mp.js index e5fa48c4..1f74c552 100644 --- a/lib/page.mp.js +++ b/lib/page.mp.js @@ -217,6 +217,13 @@ var oakBehavior = Behavior({ return this.features.runningTree.getFreshValue(path2); }, checkOperation: function (entity, action, data, filter, checkerTypes) { + if (checkerTypes === null || checkerTypes === void 0 ? void 0 : checkerTypes.includes('relation')) { + return this.features.relationAuth.checkRelation(entity, { + action: action, + data: data, + filter: filter, + }) && this.features.cache.checkOperation(entity, action, data, filter, checkerTypes); + } return this.features.cache.checkOperation(entity, action, data, filter, checkerTypes); }, tryExecute: function (path) { diff --git a/lib/page.web.js b/lib/page.web.js index 27dfb767..1b773a39 100644 --- a/lib/page.web.js +++ b/lib/page.web.js @@ -210,6 +210,13 @@ var OakComponentBase = /** @class */ (function (_super) { return this.features.runningTree.getFreshValue(path2); }; OakComponentBase.prototype.checkOperation = function (entity, action, data, filter, checkerTypes) { + if (checkerTypes === null || checkerTypes === void 0 ? void 0 : checkerTypes.includes('relation')) { + return this.features.relationAuth.checkRelation(entity, { + action: action, + data: data, + filter: filter, + }) && this.features.cache.checkOperation(entity, action, data, filter, checkerTypes); + } return this.features.cache.checkOperation(entity, action, data, filter, checkerTypes); }; OakComponentBase.prototype.tryExecute = function (path) { diff --git a/src/cacheStore/CacheStore.ts b/src/cacheStore/CacheStore.ts index 731b2996..a95e0cc7 100644 --- a/src/cacheStore/CacheStore.ts +++ b/src/cacheStore/CacheStore.ts @@ -1,19 +1,22 @@ import { AggregationResult, EntityDict, OperateOption, OperationResult, OpRecord, SelectOption } from 'oak-domain/lib/types/Entity'; import { StorageSchema } from "oak-domain/lib/types/Storage"; +import { readOnlyActions } from 'oak-domain/lib/actions/action'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; -import { Checker, CheckerType, TxnOption } from 'oak-domain/lib/types'; +import { Checker, CheckerType, Trigger, TxnOption } from 'oak-domain/lib/types'; import { TreeStore, TreeStoreOperateOption } from 'oak-memory-tree-store'; import assert from 'assert'; import { SyncContext, SyncRowStore } from 'oak-domain/lib/store/SyncRowStore'; -import CheckerExecutor from './CheckerExecutor'; +import SyncTriggerExecutor from './SyncTriggerExecutor'; -interface CachStoreOperation extends TreeStoreOperateOption {}; +interface CachStoreOperation extends TreeStoreOperateOption { + checkerTypes?: CheckerType[]; +}; export class CacheStore< ED extends EntityDict & BaseEntityDict, Cxt extends SyncContext > extends TreeStore implements SyncRowStore>{ - private checkerExecutor: CheckerExecutor; + private triggerExecutor: SyncTriggerExecutor; private getFullDataFn?: () => any; private resetInitialDataFn?: () => void; @@ -23,7 +26,7 @@ export class CacheStore< resetInitialDataFn?: () => void ) { super(storageSchema); - this.checkerExecutor = new CheckerExecutor(); + this.triggerExecutor = new SyncTriggerExecutor(); this.getFullDataFn = getFullDataFn; this.resetInitialDataFn = resetInitialDataFn; } @@ -32,21 +35,24 @@ export class CacheStore< return this.aggregateSync(entity, aggregation, context, option); } - protected cascadeUpdate(entity: T, operation: ED[T]['Operation'], context: SyncContext, option: OP): OperationResult { + protected cascadeUpdate(entity: T, operation: ED[T]['Operation'], context: SyncContext, option: OP): OperationResult { assert(context.getCurrentTxnId()); if (!option.blockTrigger) { - this.checkerExecutor.check(entity, operation, context as Cxt, 'before'); + this.triggerExecutor.check(entity, operation, context as Cxt, 'before', option.checkerTypes); } - const result = super.cascadeUpdate(entity, operation, context, option); + if (operation.data) { + const result = super.cascadeUpdate(entity, operation, context, option); + + if (!option.blockTrigger) { + this.triggerExecutor.check(entity, operation, context as Cxt, 'after', option.checkerTypes); + } - if (!option.blockTrigger) { - this.checkerExecutor.check(entity, operation, context as Cxt, 'after'); + return result; } - - return result; + return {}; } - operate( + operate( entity: T, operation: ED[T]['Operation'], context: Cxt, @@ -65,7 +71,7 @@ export class CacheStore< let result; try { - result = super.sync(opRecords, context, {}); + result = super.sync(opRecords, context, {}); } catch (err) { if (autoCommit) { context.rollback(); @@ -80,7 +86,15 @@ export class CacheStore< check(entity: T, operation: Omit, context: Cxt, checkerTypes?: CheckerType[]) { assert(context.getCurrentTxnId()); - this.checkerExecutor.check(entity, operation, context, undefined, checkerTypes); + const { action } = operation; + if (readOnlyActions.includes(action)) { + // 只读查询的checker只能在根部入口执行 + this.triggerExecutor.check(entity, operation, context, undefined, checkerTypes); + return; + } + this.operate(entity, operation as ED[T]['Operation'], context, { + checkerTypes, + }); } select< @@ -109,12 +123,12 @@ export class CacheStore< } registerChecker(checker: Checker) { - this.checkerExecutor.registerChecker(checker); + this.triggerExecutor.registerChecker(checker); } - registerGeneralChecker(type: CheckerType, fn: (entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) => void) { - this.checkerExecutor.registerGeneralChecker(type, fn); - } + /* registerTrigger(trigger: Trigger) { + this.triggerExecutor.registerTrigger(trigger); + } */ /** * 这个函数是在debug下用来获取debugStore的数据,release下不能使用 diff --git a/src/cacheStore/CheckerExecutor.ts b/src/cacheStore/CheckerExecutor.ts deleted file mode 100644 index f7f69d37..00000000 --- a/src/cacheStore/CheckerExecutor.ts +++ /dev/null @@ -1,125 +0,0 @@ -// 简化版的对checker的同步检查 -import assert from 'assert'; -import { EntityDict } from 'oak-domain/lib/types/Entity'; -import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; -import { Checker, CheckerType, SelectOption, OperateOption, DATA_CHECKER_DEFAULT_PRIORITY, CHECKER_DEFAULT_PRIORITY } from 'oak-domain/lib/types'; -import { SyncContext } from 'oak-domain/lib/store/SyncRowStore'; -import { translateCheckerInSyncContext } from 'oak-domain/lib/store/checker'; -import { checkFilterRepel } from 'oak-domain/lib/store/filter'; - -export default class CheckerExecutor> { - static All_Checker_Types: CheckerType[] = ['data', 'logical', 'logicalRelation', 'relation', 'row']; - private checkerMap: { - [K in keyof ED]?: { - [A: string]: Array<{ - priority: number; - fn: (operation: Omit, context: SyncContext, option: SelectOption | OperateOption) => void; - type: CheckerType; - when: 'before' | 'after'; - filter?: ED[K]['Update']['filter'] | ((operation: Omit, context: SyncContext, option: SelectOption | OperateOption) => ED[K]['Update']['filter']); - }>; - }; - } = {}; - private generalCheckerMap: { - [K in CheckerType]?: Array<(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) => void> - } = {}; - - registerChecker(checker: Checker) { - let { entity, action, priority, type, conditionalFilter } = checker; - const { fn, when } = translateCheckerInSyncContext(checker); - if (priority === undefined) { - priority = type === 'data' ? DATA_CHECKER_DEFAULT_PRIORITY : CHECKER_DEFAULT_PRIORITY; - } - const addCheckerMap = (action2: string) => { - if (this.checkerMap[entity] && this.checkerMap[entity]![action2]) { - let iter = 0; - const checkers = this.checkerMap[entity]![action2]!; - for (; iter < checkers.length; iter ++) { - if (priority! <= checkers[iter].priority!) { - break; - } - } - checkers.splice(iter, 0, { - type, - priority: priority!, - fn: fn as (operation: Omit, context: SyncContext, option: OperateOption | SelectOption) => void, - when, - filter: conditionalFilter, - }); - } - else if (this.checkerMap[entity]) { - Object.assign(this.checkerMap[entity]!, { - [action2]: [{ - type, - priority, - fn, - when, - filter: conditionalFilter, - }], - }); - } - else { - Object.assign(this.checkerMap, { - [entity]: { - [action2]: [{ - type, - priority, - fn, - when, - filter: conditionalFilter, - }], - }, - }); - } - }; - if (action instanceof Array) { - action.forEach( - a => addCheckerMap(a as string) - ); - } - else { - addCheckerMap(action as string); - } - } - - registerGeneralChecker(type: CheckerType, fn: (entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) => void) { - if (this.generalCheckerMap[type]) { - this.generalCheckerMap[type]?.push(fn); - } - else { - this.generalCheckerMap[type] = [fn]; - } - } - - check(entity: T, operation: Omit, context: Cxt, when?: 'before' | 'after', checkerTypes?: CheckerType[]) { - const { action } = operation; - const checkers = this.checkerMap[entity] && this.checkerMap[entity]![action]; - if (checkers) { - const checkers2 = checkers.filter(ele => (!checkerTypes || checkerTypes.includes(ele.type)) && (!when || ele.when === when)); - for (const checker of checkers2) { - const { filter } = checker; - if (filter) { - const filterr = typeof filter === 'function' ? filter(operation, context, {}) : filter; - assert(!(filter instanceof Promise), `对${entity as string}的动作${action}上定义的checker,其filter返回了Promise,请注意将同步和异步的返回区分对待`); - const isRepel = checkFilterRepel>(entity, context, filterr, operation.filter, true); - assert(typeof isRepel === 'boolean'); - if (isRepel) { - continue; - } - } - checker.fn(operation, context, {} as any); - } - } - - const types = checkerTypes || CheckerExecutor.All_Checker_Types; - types.forEach( - (type) => { - if (this.generalCheckerMap[type]) { - this.generalCheckerMap[type]?.forEach( - (fn) => fn(entity, operation, context) - ); - } - } - ) - } -} \ No newline at end of file diff --git a/src/cacheStore/SyncTriggerExecutor.ts b/src/cacheStore/SyncTriggerExecutor.ts new file mode 100644 index 00000000..d1759b7f --- /dev/null +++ b/src/cacheStore/SyncTriggerExecutor.ts @@ -0,0 +1,129 @@ +// 简化版的对checker的同步检查 +import assert from 'assert'; +import { EntityDict } from 'oak-domain/lib/types/Entity'; +import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; +import { Checker, CheckerType, SelectOption, OperateOption, CHECKER_PRIORITY_MAP, + Trigger, RemoveTrigger, UpdateTrigger, TRIGGER_DEFAULT_PRIORITY } from 'oak-domain/lib/types'; +import { SyncContext } from 'oak-domain/lib/store/SyncRowStore'; +import { translateCheckerInSyncContext } from 'oak-domain/lib/store/checker'; +import { checkFilterRepel } from 'oak-domain/lib/store/filter'; + +export default class SyncTriggerExecutor> { + static All_Checker_Types: CheckerType[] = ['data', 'logical', 'logicalRelation', 'relation', 'row']; + private checkerMap: { + [K in keyof ED]?: { + [A: string]: Array<{ + priority: number; + fn: (operation: ED[K]['Operation'], context: SyncContext, option: SelectOption | OperateOption) => void; + type: CheckerType; + when: 'before' | 'after'; + filter?: ED[K]['Update']['filter'] | ((operation: ED[K]['Operation'], context: SyncContext, option: SelectOption | OperateOption) => ED[K]['Update']['filter']); + }>; + }; + } = {}; + + private addToCheckerMap(action: string, entity: T, priority: number, when: 'before' | 'after', + fn: (operation: ED[T]['Operation'], context: SyncContext, option: SelectOption | OperateOption) => void, + type: CheckerType, + filter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: SyncContext, option: SelectOption | OperateOption) => ED[T]['Update']['filter'])) { + if (this.checkerMap[entity] && this.checkerMap[entity]![action]) { + let iter = 0; + const checkers = this.checkerMap[entity]![action]!; + for (; iter < checkers.length; iter++) { + if (priority! <= checkers[iter].priority!) { + break; + } + } + checkers.splice(iter, 0, { + type, + priority: priority!, + fn, + when, + filter, + }); + } + else if (this.checkerMap[entity]) { + Object.assign(this.checkerMap[entity]!, { + [action]: [{ + type, + priority, + fn, + when, + filter, + }], + }); + } + else { + Object.assign(this.checkerMap, { + [entity]: { + [action]: [{ + type, + priority, + fn, + when, + filter, + }], + }, + }); + } + } + + + registerChecker(checker: Checker) { + let { entity, action, priority, type, conditionalFilter } = checker; + const { fn, when } = translateCheckerInSyncContext(checker); + if (action instanceof Array) { + action.forEach( + a => this.addToCheckerMap(a as string, entity, priority || CHECKER_PRIORITY_MAP[type], when, fn as any, type, conditionalFilter) + ); + } + else { + this.addToCheckerMap(action as string, entity, priority!, when, fn as any, type, conditionalFilter); + } + } + + /* registerTrigger(trigger: Trigger) { + const { + action, + entity, + priority, + fn, + when, + filter, + } = trigger as UpdateTrigger; + + if (when === 'commit') { + return; + } + if (action instanceof Array) { + action.forEach( + (a) => this.addToCheckerMap(a, entity, priority || TRIGGER_DEFAULT_PRIORITY, when, fn as any, undefined, filter) + ); + } + else { + this.addToCheckerMap(action ,entity, priority || TRIGGER_DEFAULT_PRIORITY, when, fn as any, undefined, filter); + } + } */ + + + check(entity: T, operation: Omit, context: Cxt, when?: 'before' | 'after', checkerTypes?: CheckerType[]) { + const { action } = operation; + const checkers = this.checkerMap[entity] && this.checkerMap[entity]![action]; + if (checkers) { + const checkers2 = checkers.filter(ele => (!checkerTypes || checkerTypes.includes(ele.type)) && (!when || ele.when === when)); + for (const checker of checkers2) { + const { filter } = checker; + if (filter) { + const filterr = typeof filter === 'function' ? filter(operation as ED[T]['Operation'], context, {}) : filter; + assert(!(filter instanceof Promise), `对${entity as string}的动作${action}上定义的checker,其filter返回了Promise,请注意将同步和异步的返回区分对待`); + const isRepel = checkFilterRepel>(entity, context, filterr, operation.filter, true); + assert(typeof isRepel === 'boolean'); + if (isRepel) { + continue; + } + } + checker.fn(operation as ED[T]['Operation'], context, {} as any); + } + } + } +} \ No newline at end of file diff --git a/src/debugStore/index.ts b/src/debugStore/index.ts index 743df4c8..4359550a 100644 --- a/src/debugStore/index.ts +++ b/src/debugStore/index.ts @@ -4,7 +4,7 @@ import { DebugStore } from './DebugStore'; import { Checker, Trigger, StorageSchema, EntityDict, ActionDictOfEntityDict, Watcher, BBWatcher, WBWatcher, Routine, Timer, AuthCascadePath, AuthDeduceRelationMap} from "oak-domain/lib/types"; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; -import { analyzeActionDefDict } from 'oak-domain/lib/store/actionDef'; + import { assert } from 'oak-domain/lib/utils/assert'; import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore'; import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; @@ -268,13 +268,6 @@ export function createDebugStore store.registerTrigger(ele) - ); - adCheckers.forEach( - ele => store.registerChecker(ele) - ); // 如果没有物化数据则使用initialData初始化debugStore const data = getMaterializedData(); @@ -299,7 +292,7 @@ export function createDebugStore { name: string; @@ -30,6 +32,7 @@ export class ContextMenuFactory< cache: Cache; menuWrappers?: IMenuWrapper[]; cascadePathGraph: AuthCascadePath[]; + relationAuth: RelationAuth; private makeMenuWrappers(menus: IMenu[]): IMenuWrapper[] { const destEntities = uniq( @@ -112,10 +115,11 @@ export class ContextMenuFactory< ); } - constructor(cache: Cache, cascadePathGraph: AuthCascadePath[]) { + constructor(cache: Cache, relationAuth: RelationAuth, cascadePathGraph: AuthCascadePath[]) { super(); this.cache = cache; this.cascadePathGraph = cascadePathGraph; + this.relationAuth = relationAuth; } setMenus(menus: IMenu[]) { @@ -132,7 +136,14 @@ export class ContextMenuFactory< if (filters.length > 0) { // 这里应该是or关系,paths表达的路径中只要有一条满足就可能满足 const allows = filters.map( - (filter) => this.cache.checkOperation(destEntity, action, undefined, filter, ['logical', 'relation', 'logicalRelation', 'row']) + (filter) => { + // relationAuth和其它的checker现在分开判断 + return this.relationAuth.checkRelation(destEntity, { + action, + data: undefined as any, + filter, + } as Omit) && this.cache.checkOperation(destEntity, action, undefined, filter, ['logical', 'relation', 'logicalRelation', 'row']); + } ); if (allows.indexOf(true) >= 0) { return true; diff --git a/src/features/index.ts b/src/features/index.ts index 58f9587c..130ac90a 100644 --- a/src/features/index.ts +++ b/src/features/index.ts @@ -35,7 +35,7 @@ export function initialize) => string) { const cache = new Cache>(aspectWrapper, contextBuilder, store); const location = new Location(); - const relationAuth = new RelationAuth>(aspectWrapper, cache, + const relationAuth = new RelationAuth>(aspectWrapper, contextBuilder, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities); const runningTree = new RunningTree>(cache, storageSchema, relationAuth); const locales = new Locales(aspectWrapper, makeBridgeUrlFn); @@ -47,7 +47,7 @@ export function initialize>(aspectWrapper); const style = new Style(colorDict); - const contextMenuFactory = new ContextMenuFactory>(cache, actionCascadePathGraph); + const contextMenuFactory = new ContextMenuFactory>(cache, relationAuth, actionCascadePathGraph); return { cache, location, diff --git a/src/features/relationAuth.ts b/src/features/relationAuth.ts index 301d32a6..9a4a0248 100644 --- a/src/features/relationAuth.ts +++ b/src/features/relationAuth.ts @@ -1,4 +1,4 @@ -import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, CheckerType, Aspect, SelectOpResult, AuthCascadePath, AuthDeduceRelationMap } from 'oak-domain/lib/types'; +import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, CheckerType, Aspect, SelectOpResult, AuthCascadePath, AuthDeduceRelationMap, OakUserException } from 'oak-domain/lib/types'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { CommonAspectDict } from 'oak-common-aspect'; import { Feature } from '../types/Feature'; @@ -17,6 +17,7 @@ export class RelationAuth< AD extends CommonAspectDict & Record> > extends Feature { private cache: Cache; + private contextBuilder: () => FrontCxt; private aspectWrapper: AspectWrapper; private actionCascadePathGraph: AuthCascadePath[]; private actionCascadePathMap: Record[]>; @@ -35,6 +36,7 @@ export class RelationAuth< constructor( aspectWrapper: AspectWrapper, + contextBuilder: () => FrontCxt, cache: Cache, actionCascadePathGraph: AuthCascadePath[], relationCascadePathGraph: AuthCascadePath[], @@ -42,7 +44,8 @@ export class RelationAuth< selectFreeEntities: (keyof ED)[], ) { super(); - this.aspectWrapper = aspectWrapper, + this.aspectWrapper = aspectWrapper; + this.contextBuilder = contextBuilder; this.cache = cache; this.actionCascadePathGraph = actionCascadePathGraph; this.relationCascadePathGraph = relationCascadePathGraph; @@ -223,8 +226,21 @@ export class RelationAuth< return relationAuths; } - checkRelation(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: FrontCxt) { - this.baseRelationAuth.checkRelationSync(entity, operation, context); + checkRelation(entity: T, operation: Omit< ED[T]['Operation'] | ED[T]['Selection'], 'id'>) { + const context = this.contextBuilder(); + context.begin(); + try { + this.baseRelationAuth.checkRelationSync(entity, operation, context); + } + catch (err) { + context.rollback(); + if (!(err instanceof OakUserException)) { + throw err; + } + return false; + } + context.rollback(); + return true; } async getRelationIdByName(entity: keyof ED, name: string, entityId?: string) { diff --git a/src/initialize-dev.ts b/src/initialize-dev.ts index 5b74a7f2..d881c346 100644 --- a/src/initialize-dev.ts +++ b/src/initialize-dev.ts @@ -10,15 +10,13 @@ import { } from 'oak-domain/lib/types'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { EntityDict } from 'oak-domain/lib/types/Entity'; -import { createDynamicCheckers } from 'oak-domain/lib/checkers/index'; -import { createDynamicTriggers } from 'oak-domain/lib/triggers/index'; +import { makeIntrinsicCTWs } from 'oak-domain/lib/store/actionDef'; import { createDebugStore, clearMaterializedData } from './debugStore'; import { initialize as initBasicFeatures } from './features'; import { intersection } from 'oak-domain/lib/utils/lodash'; import commonAspectDict from 'oak-common-aspect'; -import { analyzeActionDefDict } from 'oak-domain/lib/store/actionDef'; import { CommonAspectDict, registerPorts } from 'oak-common-aspect'; import { CacheStore } from './cacheStore/CacheStore'; import { SyncContext } from 'oak-domain/lib/store/SyncRowStore'; @@ -49,7 +47,7 @@ export function initialize< frontendContextBuilder: () => (store: CacheStore) => FrontCxt, backendContextBuilder: (contextStr?: string) => (store: DebugStore) => Promise, aspectDict: AD, - triggers: Array>, + triggers: Array>, checkers: Array>, watchers: Array>, timers: Array>, @@ -68,14 +66,16 @@ export function initialize< ); } const aspectDict2 = Object.assign({}, aspectDict, commonAspectDict); - const checkers2 = (checkers).concat(createDynamicCheckers(storageSchema)); - const triggers2 = createDynamicTriggers(storageSchema).concat(triggers); + const { checkers: intCheckers, triggers: intTriggers, watchers: intWatchers } = makeIntrinsicCTWs(storageSchema, actionDict); + const checkers2 = checkers.concat(intCheckers); + const triggers2 = triggers.concat(intTriggers); + const watchers2 = watchers.concat(intWatchers); const debugStore = createDebugStore( storageSchema, backendContextBuilder, triggers2, checkers2, - watchers, + watchers2, timers, startRoutines, initialData, @@ -125,15 +125,7 @@ export function initialize< selectFreeEntities, colorDict); - checkers2.forEach((checker) => cacheStore.registerChecker(checker as Checker>)); - if (actionDict) { - const { checkers: adCheckers } = analyzeActionDefDict( - storageSchema, - actionDict - ); - adCheckers.forEach((checker) => cacheStore.registerChecker(checker)); - } - cacheStore.registerGeneralChecker('relation', (entity, operation, context) => features.relationAuth.checkRelation(entity, operation, context)); + checkers2.forEach((checker) => cacheStore.registerChecker(checker)); registerPorts(importations || [], exportations || []); diff --git a/src/initialize-prod.ts b/src/initialize-prod.ts index 753f2324..d4efde82 100644 --- a/src/initialize-prod.ts +++ b/src/initialize-prod.ts @@ -4,12 +4,13 @@ import { Checker, StorageSchema, Connector, + Trigger, } from 'oak-domain/lib/types'; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { EntityDict } from 'oak-domain/lib/types/Entity'; import { initialize as initBasicFeatures } from './features'; -import { analyzeActionDefDict } from 'oak-domain/lib/store/actionDef'; +import { makeIntrinsicCTWs } from 'oak-domain/lib/store/actionDef'; import { CommonAspectDict } from 'oak-common-aspect'; import { CacheStore } from './cacheStore/CacheStore'; import { createDynamicCheckers } from 'oak-domain/lib/checkers'; @@ -43,7 +44,10 @@ export function initialize< option: InitializeOptions ) { const { actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, actionDict, selectFreeEntities, colorDict } = option; - const checkers2 = (checkers || []).concat(createDynamicCheckers(storageSchema)); + + + const { checkers: intCheckers } = makeIntrinsicCTWs(storageSchema, actionDict); + const checkers2 = checkers.concat(intCheckers); const cacheStore = new CacheStore( storageSchema, @@ -74,15 +78,7 @@ export function initialize< (url, headers) => connector.makeBridgeUrl(url, headers) ); - checkers2.forEach((checker) => cacheStore.registerChecker(checker as Checker>)); - if (actionDict) { - const { checkers: adCheckers } = analyzeActionDefDict( - storageSchema, - actionDict - ); - adCheckers.forEach((checker) => cacheStore.registerChecker(checker)); - } - cacheStore.registerGeneralChecker('relation', (entity, operation, context) => features.relationAuth.checkRelation(entity, operation, context)); + checkers2.forEach((checker) => cacheStore.registerChecker(checker)); return { features, diff --git a/src/page.common.ts b/src/page.common.ts index 975c3d80..b5277d09 100644 --- a/src/page.common.ts +++ b/src/page.common.ts @@ -22,6 +22,7 @@ import { MessageProps } from './types/Message'; import { judgeRelation } from 'oak-domain/lib/store/relation'; import { addFilterSegment, combineFilters } from 'oak-domain/lib/store/filter'; import { MODI_NEXT_PATH_SUFFIX } from './features/runningTree'; +import { generateNewId } from 'oak-domain/lib/utils/uuid'; export async function onPathSet< ED extends EntityDict & BaseEntityDict, @@ -174,7 +175,7 @@ function checkActionsAndCascadeEntities< const filter = this.features.runningTree.getIntrinsticFilters(this.state.oakFullpath!); if (action === 'create' || typeof action === 'object' && action.action === 'create') { // 创建对象的判定不落在具体行上,但要考虑list上外键相关属性的限制 - const data = typeof action === 'object' && cloneDeep(action.data); + const data = typeof action === 'object' && Object.assign(cloneDeep(action.data) || {}, { id: generateNewId() }); if (this.checkOperation(this.state.oakEntity, 'create', data as any, filter, checkTypes)) { legalActions.push(action); } @@ -289,8 +290,9 @@ function checkActionsAndCascadeEntities< rows.forEach( (row) => { const intrinsticData = rel[1] ? { + id: generateNewId(), [rel[1]]: row.id, - } : { entity: this.state.oakEntity, entityId: row.id }; + } : { id: generateNewId(), entity: this.state.oakEntity, entityId: row.id }; if (typeof action === 'object') { Object.assign(intrinsticData, action.data); } @@ -338,8 +340,9 @@ function checkActionsAndCascadeEntities< else { if (action === 'create' || typeof action === 'object' && action.action === 'create') { const intrinsticData = rel[1] ? { + id: generateNewId(), [rel[1]]: rows.id, - } : { entity: this.state.oakEntity, entityId: rows.id }; + } : { id: generateNewId(), entity: this.state.oakEntity, entityId: rows.id }; if (typeof action === 'object') { Object.assign(intrinsticData, action.data); } diff --git a/src/page.mp.ts b/src/page.mp.ts index e8958e25..9ca7dbc4 100644 --- a/src/page.mp.ts +++ b/src/page.mp.ts @@ -320,6 +320,22 @@ const oakBehavior = Behavior< }, checkOperation(entity, action, data, filter, checkerTypes) { + if (checkerTypes?.includes('relation')) { + return this.features.relationAuth.checkRelation( + entity, + { + action, + data, + filter, + } as Omit + ) && this.features.cache.checkOperation( + entity, + action, + data, + filter, + checkerTypes + ) + } return this.features.cache.checkOperation( entity, action, diff --git a/src/page.web.tsx b/src/page.web.tsx index 482f2c27..9fd2dd4a 100644 --- a/src/page.web.tsx +++ b/src/page.web.tsx @@ -388,6 +388,22 @@ abstract class OakComponentBase< filter?: ED[T]['Update']['filter'], checkerTypes?: CheckerType[] ) { + if (checkerTypes?.includes('relation')) { + return this.features.relationAuth.checkRelation( + entity, + { + action, + data, + filter, + } as Omit + ) && this.features.cache.checkOperation( + entity, + action, + data, + filter, + checkerTypes + ) + } return this.features.cache.checkOperation( entity, action,