适配了domain的定义性更新,重构了不少代码流程

This commit is contained in:
Xu Chang 2023-07-18 18:54:26 +08:00
parent d683d2e709
commit ef6175313f
32 changed files with 495 additions and 253 deletions

View File

@ -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<ED extends EntityDict & BaseEntityDict, Cxt extends SyncContext<ED>> extends TreeStore<ED> implements SyncRowStore<ED, SyncContext<ED>> {
private checkerExecutor;
private triggerExecutor;
private getFullDataFn?;
private resetInitialDataFn?;
constructor(storageSchema: StorageSchema<ED>, getFullDataFn?: () => any, resetInitialDataFn?: () => void);
aggregate<T extends keyof ED, OP extends SelectOption>(entity: T, aggregation: ED[T]['Aggregation'], context: SyncContext<ED>, option: OP): AggregationResult<ED[T]['Schema']>;
protected cascadeUpdate<T extends keyof ED, OP extends OperateOption>(entity: T, operation: ED[T]['Operation'], context: SyncContext<ED>, option: OP): OperationResult<ED>;
operate<T extends keyof ED, OP extends TreeStoreOperateOption>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED>;
protected cascadeUpdate<T extends keyof ED, OP extends CachStoreOperation>(entity: T, operation: ED[T]['Operation'], context: SyncContext<ED>, option: OP): OperationResult<ED>;
operate<T extends keyof ED, OP extends CachStoreOperation>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult<ED>;
sync<Cxt extends SyncContext<ED>>(opRecords: Array<OpRecord<ED>>, context: Cxt): void;
check<T extends keyof ED>(entity: T, operation: Omit<ED[T]['Operation'], 'id'>, context: Cxt, checkerTypes?: CheckerType[]): void;
select<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial<ED[T]["Schema"]>[];
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>): void;
registerGeneralChecker(type: CheckerType, fn: <T extends keyof ED>(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<ED extends EntityDict & BaseEntityDict, Cxt exte
commit(txnId: string): void;
rollback(txnId: string): void;
}
export {};

View File

@ -2,15 +2,16 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheStore = void 0;
var tslib_1 = require("tslib");
var action_1 = require("oak-domain/lib/actions/action");
var oak_memory_tree_store_1 = require("oak-memory-tree-store");
var assert_1 = tslib_1.__importDefault(require("assert"));
var CheckerExecutor_1 = tslib_1.__importDefault(require("./CheckerExecutor"));
var SyncTriggerExecutor_1 = tslib_1.__importDefault(require("./SyncTriggerExecutor"));
;
var CacheStore = /** @class */ (function (_super) {
tslib_1.__extends(CacheStore, _super);
function CacheStore(storageSchema, getFullDataFn, resetInitialDataFn) {
var _this = _super.call(this, storageSchema) || this;
_this.checkerExecutor = new CheckerExecutor_1.default();
_this.triggerExecutor = new SyncTriggerExecutor_1.default();
_this.getFullDataFn = getFullDataFn;
_this.resetInitialDataFn = resetInitialDataFn;
return _this;
@ -21,13 +22,16 @@ var CacheStore = /** @class */ (function (_super) {
CacheStore.prototype.cascadeUpdate = function (entity, operation, context, option) {
(0, assert_1.default)(context.getCurrentTxnId());
if (!option.blockTrigger) {
this.checkerExecutor.check(entity, operation, context, 'before');
this.triggerExecutor.check(entity, operation, context, 'before', option.checkerTypes);
}
var result = _super.prototype.cascadeUpdate.call(this, entity, operation, context, option);
if (!option.blockTrigger) {
this.checkerExecutor.check(entity, operation, context, 'after');
if (operation.data) {
var result = _super.prototype.cascadeUpdate.call(this, entity, operation, context, option);
if (!option.blockTrigger) {
this.triggerExecutor.check(entity, operation, context, 'after', option.checkerTypes);
}
return result;
}
return result;
return {};
};
CacheStore.prototype.operate = function (entity, operation, context, option) {
(0, assert_1.default)(context.getCurrentTxnId());
@ -56,7 +60,15 @@ var CacheStore = /** @class */ (function (_super) {
};
CacheStore.prototype.check = function (entity, operation, context, checkerTypes) {
(0, assert_1.default)(context.getCurrentTxnId());
this.checkerExecutor.check(entity, operation, context, undefined, checkerTypes);
var action = operation.action;
if (action_1.readOnlyActions.includes(action)) {
// 只读查询的checker只能在根部入口执行
this.triggerExecutor.check(entity, operation, context, undefined, checkerTypes);
return;
}
this.operate(entity, operation, context, {
checkerTypes: checkerTypes,
});
};
CacheStore.prototype.select = function (entity, selection, context, option) {
var autoCommit = !context.getCurrentTxnId();
@ -79,11 +91,11 @@ var CacheStore = /** @class */ (function (_super) {
return result;
};
CacheStore.prototype.registerChecker = function (checker) {
this.checkerExecutor.registerChecker(checker);
};
CacheStore.prototype.registerGeneralChecker = function (type, fn) {
this.checkerExecutor.registerGeneralChecker(type, fn);
this.triggerExecutor.registerChecker(checker);
};
/* registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>) {
this.triggerExecutor.registerTrigger(trigger);
} */
/**
* 这个函数是在debug下用来获取debugStore的数据release下不能使用
* @returns

11
lib/cacheStore/SyncTriggerExecutor.d.ts vendored Normal file
View File

@ -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<ED extends EntityDict & BaseEntityDict, Cxt extends SyncContext<ED>> {
static All_Checker_Types: CheckerType[];
private checkerMap;
private addToCheckerMap;
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>): void;
check<T extends keyof ED>(entity: T, operation: Omit<ED[T]['Operation'], 'id'>, context: Cxt, when?: 'before' | 'after', checkerTypes?: CheckerType[]): void;
}

View File

@ -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<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>) {
const {
action,
entity,
priority,
fn,
when,
filter,
} = trigger as UpdateTrigger<ED, T, Cxt>;
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;

View File

@ -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);

View File

@ -46,7 +46,7 @@ export declare class Cache<ED extends EntityDict & BaseEntityDict, Cxt extends A
}>, context: FrontCxt): void;
private getInner;
get<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context?: FrontCxt, allowMiss?: boolean): Partial<ED[T]["Schema"]>[];
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<ED>[]) => void): void;
unbindOnSync(callback: (opRecords: OpRecord<ED>[]) => void): void;
getCachedData(): { [T in keyof ED]?: ED[T]["OpSchema"][] | undefined; };

View File

@ -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);

View File

@ -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<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
name: string;
entity: T;
@ -18,8 +19,9 @@ export declare class ContextMenuFactory<ED extends EntityDict & BaseEntityDict,
cache: Cache<ED, Cxt, FrontCxt, AD>;
menuWrappers?: IMenuWrapper<ED, keyof ED>[];
cascadePathGraph: AuthCascadePath<ED>[];
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>;
private makeMenuWrappers;
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]);
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]);
setMenus(menus: IMenu<ED, keyof ED>[]): void;
getMenusByContext<OMenu extends IMenu<ED, keyof ED>>(entity: keyof ED, entityId: string): OMenu[];
}

View File

@ -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;
}

View File

@ -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,

View File

@ -7,6 +7,7 @@ import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { Cache } from './cache';
export declare class RelationAuth<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>> extends Feature {
private cache;
private contextBuilder;
private aspectWrapper;
private actionCascadePathGraph;
private actionCascadePathMap;
@ -15,7 +16,7 @@ export declare class RelationAuth<ED extends EntityDict & BaseEntityDict, Cxt ex
private authDeduceRelationMap;
static IgnoredActions: string[];
private entityGraph?;
constructor(aspectWrapper: AspectWrapper<ED, Cxt, AD>, cache: Cache<ED, Cxt, FrontCxt, AD>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities: (keyof ED)[]);
constructor(aspectWrapper: AspectWrapper<ED, Cxt, AD>, contextBuilder: () => FrontCxt, cache: Cache<ED, Cxt, FrontCxt, AD>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities: (keyof ED)[]);
private judgeRelation;
getHasRelationEntities(): string[];
getDeduceRelationAttribute(entity: keyof ED): string | undefined;
@ -40,6 +41,6 @@ export declare class RelationAuth<ED extends EntityDict & BaseEntityDict, Cxt ex
getCascadeActionAuths(entity: keyof ED, ir: boolean): AuthCascadePath<ED>[];
getCascadeRelationAuthsBySource(entity: keyof ED): AuthCascadePath<ED>[];
getCascadeRelationAuths(entity: keyof ED, ir: boolean): AuthCascadePath<ED>[];
checkRelation<T extends keyof ED>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: FrontCxt): void;
checkRelation<T extends keyof ED>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>): boolean;
getRelationIdByName(entity: keyof ED, name: string, entityId?: string): Promise<ED["relation"]["Schema"]["id"] | undefined>;
}

View File

@ -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 () {

View File

@ -50,7 +50,7 @@ declare abstract class Node<ED extends EntityDict & BaseEntityDict, T extends ke
getParent(): SingleNode<ED, keyof ED, Cxt, FrontCxt, AD> | ListNode<ED, T, Cxt, FrontCxt, AD> | VirtualNode<ED, Cxt, FrontCxt, AD> | 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;
}

View File

@ -20,7 +20,7 @@ import { InitializeOptions } from './types/Initialize';
* @param actionDict
* @returns
*/
export declare function initialize<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>>(storageSchema: StorageSchema<ED>, frontendContextBuilder: () => (store: CacheStore<ED, FrontCxt>) => FrontCxt, backendContextBuilder: (contextStr?: string) => (store: DebugStore<ED, Cxt>) => Promise<Cxt>, aspectDict: AD, triggers: Array<Trigger<ED, keyof ED, Cxt>>, checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>, watchers: Array<Watcher<ED, keyof ED, Cxt>>, timers: Array<Timer<ED, Cxt>>, startRoutines: Array<Routine<ED, Cxt>>, initialData: {
export declare function initialize<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>>(storageSchema: StorageSchema<ED>, frontendContextBuilder: () => (store: CacheStore<ED, FrontCxt>) => FrontCxt, backendContextBuilder: (contextStr?: string) => (store: DebugStore<ED, Cxt>) => Promise<Cxt>, aspectDict: AD, triggers: Array<Trigger<ED, keyof ED, Cxt | FrontCxt>>, checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>, watchers: Array<Watcher<ED, keyof ED, Cxt>>, timers: Array<Timer<ED, Cxt>>, startRoutines: Array<Routine<ED, Cxt>>, initialData: {
[T in keyof ED]?: Array<ED[T]['OpSchema']>;
}, option: InitializeOptions<ED>): {
features: import("./features").BasicFeatures<ED, Cxt, FrontCxt, CommonAspectDict<ED, Cxt> & AD>;

View File

@ -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,

View File

@ -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,
};

View File

@ -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);
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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<ED>
> extends TreeStore<ED> implements SyncRowStore<ED, SyncContext<ED>>{
private checkerExecutor: CheckerExecutor<ED, Cxt>;
private triggerExecutor: SyncTriggerExecutor<ED, Cxt>;
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<T extends keyof ED, OP extends OperateOption>(entity: T, operation: ED[T]['Operation'], context: SyncContext<ED>, option: OP): OperationResult<ED> {
protected cascadeUpdate<T extends keyof ED, OP extends CachStoreOperation>(entity: T, operation: ED[T]['Operation'], context: SyncContext<ED>, option: OP): OperationResult<ED> {
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<T extends keyof ED, OP extends TreeStoreOperateOption>(
operate<T extends keyof ED, OP extends CachStoreOperation>(
entity: T,
operation: ED[T]['Operation'],
context: Cxt,
@ -65,7 +71,7 @@ export class CacheStore<
let result;
try {
result = super.sync<CachStoreOperation, Cxt>(opRecords, context, {});
result = super.sync<OperateOption, Cxt>(opRecords, context, {});
} catch (err) {
if (autoCommit) {
context.rollback();
@ -80,7 +86,15 @@ export class CacheStore<
check<T extends keyof ED>(entity: T, operation: Omit<ED[T]['Operation'], 'id'>, 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<T extends keyof ED>(checker: Checker<ED, T, Cxt>) {
this.checkerExecutor.registerChecker(checker);
this.triggerExecutor.registerChecker(checker);
}
registerGeneralChecker(type: CheckerType, fn: <T extends keyof ED>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) => void) {
this.checkerExecutor.registerGeneralChecker(type, fn);
}
/* registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>) {
this.triggerExecutor.registerTrigger(trigger);
} */
/**
* debug下用来获取debugStore的数据release下不能使用

View File

@ -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<ED extends EntityDict & BaseEntityDict, Cxt extends SyncContext<ED>> {
static All_Checker_Types: CheckerType[] = ['data', 'logical', 'logicalRelation', 'relation', 'row'];
private checkerMap: {
[K in keyof ED]?: {
[A: string]: Array<{
priority: number;
fn: (operation: Omit<ED[K]['Operation'], 'id'>, context: SyncContext<ED>, option: SelectOption | OperateOption) => void;
type: CheckerType;
when: 'before' | 'after';
filter?: ED[K]['Update']['filter'] | ((operation: Omit<ED[K]['Operation'], 'id'>, context: SyncContext<ED>, option: SelectOption | OperateOption) => ED[K]['Update']['filter']);
}>;
};
} = {};
private generalCheckerMap: {
[K in CheckerType]?: Array<<T extends keyof ED>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) => void>
} = {};
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>) {
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<ED[T]['Operation'], 'id'>, context: SyncContext<ED>, 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: <T extends keyof ED>(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<T extends keyof ED>(entity: T, operation: Omit<ED[T]['Operation'], 'id'>, 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<ED, T, SyncContext<ED>>(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)
);
}
}
)
}
}

View File

@ -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<ED extends EntityDict & BaseEntityDict, Cxt extends SyncContext<ED>> {
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<ED>, option: SelectOption | OperateOption) => void;
type: CheckerType;
when: 'before' | 'after';
filter?: ED[K]['Update']['filter'] | ((operation: ED[K]['Operation'], context: SyncContext<ED>, option: SelectOption | OperateOption) => ED[K]['Update']['filter']);
}>;
};
} = {};
private addToCheckerMap<T extends keyof ED>(action: string, entity: T, priority: number, when: 'before' | 'after',
fn: (operation: ED[T]['Operation'], context: SyncContext<ED>, option: SelectOption | OperateOption) => void,
type: CheckerType,
filter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: SyncContext<ED>, 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<T extends keyof ED>(checker: Checker<ED, T, Cxt>) {
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<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>) {
const {
action,
entity,
priority,
fn,
when,
filter,
} = trigger as UpdateTrigger<ED, T, Cxt>;
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<T extends keyof ED>(entity: T, operation: Omit<ED[T]['Operation'], 'id'>, 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<ED, T, SyncContext<ED>>(entity, context, filterr, operation.filter, true);
assert(typeof isRepel === 'boolean');
if (isRepel) {
continue;
}
}
checker.fn(operation as ED[T]['Operation'], context, {} as any);
}
}
}
}

View File

@ -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<ED extends EntityDict & BaseEntityDict, Cxt ext
);
assert(actionDict);
const { triggers: adTriggers, checkers: adCheckers, watchers: adWatchers } = analyzeActionDefDict(storageSchema, actionDict!);
adTriggers.forEach(
ele => store.registerTrigger(ele)
);
adCheckers.forEach(
ele => store.registerChecker(ele)
);
// 如果没有物化数据则使用initialData初始化debugStore
const data = getMaterializedData();
@ -299,7 +292,7 @@ export function createDebugStore<ED extends EntityDict & BaseEntityDict, Cxt ext
}, 10000);
// 启动watcher
initializeWatchers(store, contextBuilder, watchers.concat(adWatchers));
initializeWatchers(store, contextBuilder, watchers);
// 启动timer
if (timers) {
initializeTimers(store, contextBuilder, timers);

View File

@ -8,6 +8,7 @@ import { OakRowUnexistedException, OakRowInconsistencyException, OakException, O
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import assert from 'assert';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
export class Cache<
ED extends EntityDict & BaseEntityDict,
@ -210,7 +211,7 @@ export class Cache<
const operation = {
action,
filter,
data,
data
} as ED[T]['Update'];
try {
this.cacheStore!.check(entity, operation, context, checkerTypes);

View File

@ -9,6 +9,8 @@ import { combineFilters } from 'oak-domain/lib/store/filter';
import { Cache } from './cache';
import { Feature } from '../types/Feature';
import { judgeRelation } from 'oak-domain/lib/store/relation';
import { RelationAuth } from './relationAuth';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
interface IMenu<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
name: string;
@ -30,6 +32,7 @@ export class ContextMenuFactory<
cache: Cache<ED, Cxt, FrontCxt, AD>;
menuWrappers?: IMenuWrapper<ED, keyof ED>[];
cascadePathGraph: AuthCascadePath<ED>[];
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>;
private makeMenuWrappers(menus: IMenu<ED, keyof ED>[]): IMenuWrapper<ED, keyof ED>[] {
const destEntities = uniq(
@ -112,10 +115,11 @@ export class ContextMenuFactory<
);
}
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]) {
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]) {
super();
this.cache = cache;
this.cascadePathGraph = cascadePathGraph;
this.relationAuth = relationAuth;
}
setMenus(menus: IMenu<ED, keyof ED>[]) {
@ -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<ED[keyof ED]['Operation'], 'id'>) && this.cache.checkOperation(destEntity, action, undefined, filter, ['logical', 'relation', 'logicalRelation', 'row']);
}
);
if (allows.indexOf(true) >= 0) {
return true;

View File

@ -35,7 +35,7 @@ export function initialize<ED extends EntityDict & BaseEntityDict, Cxt extends A
makeBridgeUrlFn?: (url: string, headers?: Record<string, string>) => string) {
const cache = new Cache<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper, contextBuilder, store);
const location = new Location();
const relationAuth = new RelationAuth<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper, cache,
const relationAuth = new RelationAuth<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper, contextBuilder, cache,
actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities);
const runningTree = new RunningTree<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(cache, storageSchema, relationAuth);
const locales = new Locales(aspectWrapper, makeBridgeUrlFn);
@ -47,7 +47,7 @@ export function initialize<ED extends EntityDict & BaseEntityDict, Cxt extends A
const navigator = new Navigator();
const port = new Port<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper);
const style = new Style<ED>(colorDict);
const contextMenuFactory = new ContextMenuFactory<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(cache, actionCascadePathGraph);
const contextMenuFactory = new ContextMenuFactory<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(cache, relationAuth, actionCascadePathGraph);
return {
cache,
location,

View File

@ -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<ED, Cxt> & Record<string, Aspect<ED, Cxt>>
> extends Feature {
private cache: Cache<ED, Cxt, FrontCxt, AD>;
private contextBuilder: () => FrontCxt;
private aspectWrapper: AspectWrapper<ED, Cxt, AD>;
private actionCascadePathGraph: AuthCascadePath<ED>[];
private actionCascadePathMap: Record<string, AuthCascadePath<ED>[]>;
@ -35,6 +36,7 @@ export class RelationAuth<
constructor(
aspectWrapper: AspectWrapper<ED, Cxt, AD>,
contextBuilder: () => FrontCxt,
cache: Cache<ED, Cxt, FrontCxt, AD>,
actionCascadePathGraph: AuthCascadePath<ED>[],
relationCascadePathGraph: AuthCascadePath<ED>[],
@ -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<T extends keyof ED>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: FrontCxt) {
this.baseRelationAuth.checkRelationSync(entity, operation, context);
checkRelation<T extends keyof ED>(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) {

View File

@ -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<ED, FrontCxt>) => FrontCxt,
backendContextBuilder: (contextStr?: string) => (store: DebugStore<ED, Cxt>) => Promise<Cxt>,
aspectDict: AD,
triggers: Array<Trigger<ED, keyof ED, Cxt>>,
triggers: Array<Trigger<ED, keyof ED, Cxt | FrontCxt>>,
checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>,
watchers: Array<Watcher<ED, keyof ED, Cxt>>,
timers: Array<Timer<ED, Cxt>>,
@ -68,14 +66,16 @@ export function initialize<
);
}
const aspectDict2 = Object.assign({}, aspectDict, commonAspectDict);
const checkers2 = (checkers).concat(createDynamicCheckers<ED, Cxt | FrontCxt>(storageSchema));
const triggers2 = createDynamicTriggers<ED, Cxt>(storageSchema).concat(triggers);
const { checkers: intCheckers, triggers: intTriggers, watchers: intWatchers } = makeIntrinsicCTWs<ED, Cxt, FrontCxt>(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<ED, keyof ED, SyncContext<ED>>));
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 || []);

View File

@ -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<ED>
) {
const { actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, actionDict, selectFreeEntities, colorDict } = option;
const checkers2 = (checkers || []).concat(createDynamicCheckers<ED, Cxt | FrontCxt>(storageSchema));
const { checkers: intCheckers } = makeIntrinsicCTWs<ED, Cxt, FrontCxt>(storageSchema, actionDict);
const checkers2 = checkers.concat(intCheckers);
const cacheStore = new CacheStore<ED, FrontCxt>(
storageSchema,
@ -74,15 +78,7 @@ export function initialize<
(url, headers) => connector.makeBridgeUrl(url, headers)
);
checkers2.forEach((checker) => cacheStore.registerChecker(checker as Checker<ED, keyof ED, SyncContext<ED>>));
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,

View File

@ -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);
}

View File

@ -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<EDD[keyof EDD]['Operation'], 'id'>
) && this.features.cache.checkOperation(
entity,
action,
data,
filter,
checkerTypes
)
}
return this.features.cache.checkOperation(
entity,
action,

View File

@ -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<ED[keyof ED]['Operation'], 'id'>
) && this.features.cache.checkOperation(
entity,
action,
data,
filter,
checkerTypes
)
}
return this.features.cache.checkOperation(
entity,
action,