增加了cache的缓存和keepFresh行为

This commit is contained in:
Xu Chang 2023-08-21 15:29:22 +08:00
parent b232d0c4c4
commit 8eba310582
29 changed files with 766 additions and 259 deletions

View File

@ -2,4 +2,6 @@ export declare const LOCAL_STORAGE_KEYS: {
debugStore: string;
debugStoreStat: string;
localeLng: string;
cacheSaved: string;
cacheRefreshRecord: string;
};

View File

@ -2,7 +2,9 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.LOCAL_STORAGE_KEYS = void 0;
exports.LOCAL_STORAGE_KEYS = {
debugStore: 'oak-fronted-base:debugStore',
debugStoreStat: 'oak-fronted-base:debugStoreStat',
localeLng: 'oak-frontend-base:feature-locale-lng',
debugStore: 'ofd:ds',
debugStoreStat: 'ofd:dss',
localeLng: 'ofd:f-l-l',
cacheSaved: 'ofd:f-c-s',
cacheRefreshRecord: 'ofd:f-c-rr',
};

View File

@ -12,7 +12,7 @@ interface DebugStoreSelectOption extends TreeStoreSelectOption {
export declare class DebugStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends TreeStore<ED> implements AsyncRowStore<ED, Cxt> {
private executor;
private relationAuth;
constructor(storageSchema: StorageSchema<ED>, contextBuilder: (cxtString?: string) => (store: DebugStore<ED, Cxt>) => Promise<Cxt>, actionCascadeGraph: AuthCascadePath<ED>[], relationCascadeGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities: (keyof ED)[], createFreeEntities: (keyof ED)[], updateFreeEntities: (keyof ED)[]);
constructor(storageSchema: StorageSchema<ED>, contextBuilder: (cxtString?: string) => (store: DebugStore<ED, Cxt>) => Promise<Cxt>, actionCascadeGraph: AuthCascadePath<ED>[], relationCascadeGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: (keyof ED)[], createFreeEntities?: (keyof ED)[], updateFreeEntities?: (keyof ED)[]);
exec(script: string, txnId?: string): Promise<void>;
aggregate<T extends keyof ED, OP extends SelectOption>(entity: T, aggregation: ED[T]["Aggregation"], context: Cxt, option: OP): Promise<AggregationResult<ED[T]["Schema"]>>;
begin(option?: TxnOption): Promise<string>;

View File

@ -5,4 +5,4 @@ import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
export declare function clearMaterializedData(): void;
export declare function createDebugStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>>(storageSchema: StorageSchema<ED>, contextBuilder: (cxtString?: string) => (store: DebugStore<ED, Cxt>) => Promise<Cxt>, triggers: Array<Trigger<ED, keyof ED, Cxt>>, checkers: Array<Checker<ED, keyof ED, 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']>;
}, actionDict: ActionDictOfEntityDict<ED>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities: (keyof ED)[], createFreeEntities: (keyof ED)[], updateFreeEntities: (keyof ED)[], saveFn: (key: string, data: any) => void, loadFn: (key: string) => any): DebugStore<ED, Cxt>;
}, actionDict: ActionDictOfEntityDict<ED>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, saveFn: (key: string, data: any) => void, loadFn: (key: string) => any, selectFreeEntities?: (keyof ED)[], createFreeEntities?: (keyof ED)[], updateFreeEntities?: (keyof ED)[]): DebugStore<ED, Cxt>;

View File

@ -340,7 +340,7 @@ function doRoutines(store, contextBuilder, routines) {
});
});
}
function createDebugStore(storageSchema, contextBuilder, triggers, checkers, watchers, timers, startRoutines, initialData, actionDict, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities, saveFn, loadFn) {
function createDebugStore(storageSchema, contextBuilder, triggers, checkers, watchers, timers, startRoutines, initialData, actionDict, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, saveFn, loadFn, selectFreeEntities, createFreeEntities, updateFreeEntities) {
var store = new DebugStore_1.DebugStore(storageSchema, contextBuilder, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities);
triggers.forEach(function (ele) { return store.registerTrigger(ele); });
checkers.forEach(function (ele) { return store.registerChecker(ele); });

View File

@ -1,25 +1,57 @@
import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, CheckerType, Aspect } from 'oak-domain/lib/types';
import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, CheckerType, Aspect, StorageSchema, Checker } 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';
import { CacheStore } from '../cacheStore/CacheStore';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { LocalStorage } from './localStorage';
interface CacheSelectOption extends SelectOption {
ignoreKeepFreshRule?: true;
}
export declare class Cache<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>> extends Feature {
cacheStore: CacheStore<ED, FrontCxt>;
private aspectWrapper;
private syncEventsCallbacks;
private contextBuilder?;
private refreshing;
private savedEntities;
private keepFreshPeriod;
private localStorage;
private getFullDataFn;
constructor(aspectWrapper: AspectWrapper<ED, Cxt, AD>, contextBuilder: () => FrontCxt, store: CacheStore<ED, FrontCxt>, getFullData: () => any);
getSchema(): import("oak-domain/lib/types").StorageSchema<ED>;
private refreshRecords;
constructor(storageSchema: StorageSchema<ED>, aspectWrapper: AspectWrapper<ED, Cxt, AD>, frontendContextBuilder: () => (store: CacheStore<ED, FrontCxt>) => FrontCxt, checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>, getFullData: () => any, localStorage: LocalStorage, savedEntities?: (keyof ED)[], keepFreshPeriod?: number);
/**
* cache中需要缓存的数据
*/
private initSavedLogic;
getSchema(): StorageSchema<ED>;
getCurrentUserId(allowUnloggedIn?: boolean): string | undefined;
exec<K extends keyof AD>(name: K, params: Parameters<AD[K]>[0], callback?: (result: Awaited<ReturnType<AD[K]>>, opRecords?: OpRecord<ED>[]) => void, dontPublish?: true): Promise<{
result: Awaited<ReturnType<AD[K]>>;
message: string | null | undefined;
}>;
refresh<T extends keyof ED, OP extends SelectOption>(entity: T, selection: ED[T]['Selection'], option?: OP, getCount?: true, callback?: (result: Awaited<ReturnType<AD['select']>>) => void, dontPublish?: true): Promise<{
private getRefreshRecordSize;
private reduceRefreshRecord;
private addRefreshRecord;
/**
* refresh行为是否可以应用缓存优化
* selection必须满足
* 1indexFrom和count
* 2getCount
* 3projection和filter只限定在该对象自身属性上
* 4filter
* @param entity
* @param selection
* @param option
* @param getCount
*/
private canOptimizeRefresh;
private filterToKey;
refresh<T extends keyof ED, OP extends CacheSelectOption>(entity: T, selection: ED[T]['Selection'], option?: OP, getCount?: true, callback?: (result: Awaited<ReturnType<AD['select']>>) => void, dontPublish?: true): Promise<{
data: Partial<ED[T]["Schema"]>[];
count?: undefined;
} | {
data: Partial<ED[T]["Schema"]>[];
count: number | undefined;
}>;
@ -56,3 +88,4 @@ export declare class Cache<ED extends EntityDict & BaseEntityDict, Cxt extends A
commit(context: FrontCxt): void;
rollback(context: FrontCxt): void;
}
export {};

View File

@ -4,33 +4,57 @@ exports.Cache = void 0;
var tslib_1 = require("tslib");
var Feature_1 = require("../types/Feature");
var lodash_1 = require("oak-domain/lib/utils/lodash");
var CacheStore_1 = require("../cacheStore/CacheStore");
var Exception_1 = require("oak-domain/lib/types/Exception");
var assert_1 = tslib_1.__importDefault(require("assert"));
var constant_1 = require("../constant/constant");
var filter_1 = require("oak-domain/lib/store/filter");
var DEFAULT_KEEP_FRESH_PERIOD = 600 * 1000; // 10分钟不刷新
;
var Cache = /** @class */ (function (_super) {
tslib_1.__extends(Cache, _super);
function Cache(aspectWrapper, contextBuilder, store, getFullData) {
function Cache(storageSchema, aspectWrapper, frontendContextBuilder, checkers, getFullData, localStorage, savedEntities, keepFreshPeriod) {
var _this = _super.call(this) || this;
_this.refreshing = false;
_this.refreshRecords = {};
_this.aspectWrapper = aspectWrapper;
_this.syncEventsCallbacks = [];
_this.contextBuilder = contextBuilder;
_this.cacheStore = store;
_this.cacheStore = new CacheStore_1.CacheStore(storageSchema);
_this.contextBuilder = function () { return frontendContextBuilder()(_this.cacheStore); };
_this.savedEntities = tslib_1.__spreadArray(['actionAuth', 'i18n'], tslib_1.__read((savedEntities || [])), false);
_this.keepFreshPeriod = keepFreshPeriod || DEFAULT_KEEP_FRESH_PERIOD;
_this.localStorage = localStorage;
checkers.forEach(function (checker) { return _this.cacheStore.registerChecker(checker); });
_this.getFullDataFn = getFullData;
_this.initSavedLogic();
return _this;
// 在这里把wrapper的返回opRecords截取到并同步到cache中
/* const { exec } = aspectWrapper;
aspectWrapper.exec = async <T extends keyof AD>(
name: T,
params: any
) => {
const { result, opRecords } = await exec(name, params);
this.sync(opRecords);
return {
result,
opRecords,
};
}; */
}
/**
* 处理cache中需要缓存的数据
*/
Cache.prototype.initSavedLogic = function () {
var _this = this;
var data = {};
this.savedEntities.forEach(function (entity) {
var key = "".concat(constant_1.LOCAL_STORAGE_KEYS.cacheSaved, ":").concat(entity);
var cached = _this.localStorage.load(key);
if (cached) {
data[entity] = cached;
}
});
this.cacheStore.resetInitialData(data);
this.cacheStore.onCommit(function (result) {
var entities = Object.keys(result);
var referenced = (0, lodash_1.intersection)(entities, _this.savedEntities);
if (referenced.length > 0) {
var saved_1 = _this.cacheStore.getCurrentData(referenced);
Object.keys(saved_1).forEach(function (entity) {
var key = "".concat(constant_1.LOCAL_STORAGE_KEYS.cacheSaved, ":").concat(entity);
_this.localStorage.save(key, saved_1[entity]);
});
}
});
};
Cache.prototype.getSchema = function () {
return this.cacheStore.getSchema();
};
@ -76,21 +100,231 @@ var Cache = /** @class */ (function (_super) {
});
});
};
Cache.prototype.getRefreshRecordSize = function () {
var _this = this;
var count = 0;
Object.keys(this.refreshRecords).forEach(function (entity) { return count += Object.keys(_this.refreshRecords[entity]).length; });
return count;
};
Cache.prototype.reduceRefreshRecord = function (now) {
var _this = this;
Object.keys(this.refreshRecords).forEach(function (ele) {
if (!_this.savedEntities.includes(ele)) {
var outdated = [];
for (var filter in _this.refreshRecords[ele]) {
if (_this.refreshRecords[ele][filter] < now - _this.keepFreshPeriod) {
outdated.push(filter);
}
}
_this.refreshRecords[ele] = (0, lodash_1.omit)(_this.refreshRecords[ele], outdated);
}
});
};
Cache.prototype.addRefreshRecord = function (entity, filter, now) {
var _a, _b, _c;
var _this = this;
var originTimestamp = this.refreshRecords[entity] && this.refreshRecords[entity][filter];
if (this.refreshRecords[entity]) {
Object.assign(this.refreshRecords[entity], (_a = {},
_a[filter] = now,
_a));
}
else {
Object.assign(this.refreshRecords, (_b = {},
_b[entity] = (_c = {},
_c[filter] = now,
_c),
_b));
}
var count = this.getRefreshRecordSize();
if (count > 100) {
count = this.getRefreshRecordSize();
this.reduceRefreshRecord(now);
if (count > 100) {
console.warn('cache中的refreshRecord数量过多请检查是否因为存在带有Date.now等变量的刷新例程', this.refreshRecords);
}
}
if (originTimestamp) {
return function () { return _this.addRefreshRecord(entity, filter, originTimestamp); };
}
};
/**
* 判定一个refresh行为是否可以应用缓存优化
* 可以优化的selection必须满足
* 1没有indexFrom和count
* 2没要求getCount
* 3查询的projection和filter只限定在该对象自身属性上
* 4有filter
* @param entity
* @param selection
* @param option
* @param getCount
*/
Cache.prototype.canOptimizeRefresh = function (entity, selection, option, getCount) {
var e_2, _a;
var _this = this;
if (getCount || selection.indexFrom || selection.count || selection.randomRange || !selection.filter || (option === null || option === void 0 ? void 0 : option.ignoreKeepFreshRule)) {
return false;
}
var data = selection.data, filter = selection.filter, sorter = selection.sorter;
// projection中不能有cascade查询或者表达式
var checkProjection = function (projection) {
for (var attr in data) {
var rel = _this.judgeRelation(entity, attr);
if (typeof rel !== 'number' || ![0, 1].includes(rel)) {
return false;
}
}
return true;
};
if (!checkProjection(data)) {
return false;
}
// filter中不能有cascade查询或者表达式
var checkFilter = function (filter2) {
var e_3, _a;
for (var attr in filter2) {
if (['$and', '$or'].includes(attr)) {
try {
for (var _b = (e_3 = void 0, tslib_1.__values(filter2[attr])), _c = _b.next(); !_c.done; _c = _b.next()) {
var f2 = _c.value;
if (!checkFilter(f2)) {
return false;
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_3) throw e_3.error; }
}
}
else if (attr === '$not') {
if (!checkFilter(filter2[attr])) {
return false;
}
}
else if (!attr.startsWith('$') || !attr.startsWith('#')) {
var rel = _this.judgeRelation(entity, attr);
if (typeof rel !== 'number' || ![0, 1].includes(rel)) {
return false;
}
}
}
return true;
};
if (!checkFilter(filter)) {
return false;
}
if (sorter) {
try {
for (var sorter_1 = tslib_1.__values(sorter), sorter_1_1 = sorter_1.next(); !sorter_1_1.done; sorter_1_1 = sorter_1.next()) {
var s = sorter_1_1.value;
if (!checkProjection(s.$attr)) {
return false;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (sorter_1_1 && !sorter_1_1.done && (_a = sorter_1.return)) _a.call(sorter_1);
}
finally { if (e_2) throw e_2.error; }
}
}
return true;
};
Cache.prototype.filterToKey = function (filter) {
return JSON.stringify(filter);
};
Cache.prototype.refresh = function (entity, selection, option, getCount, callback, dontPublish) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _a, ids, count, aggr, selection2, data;
var canOptimize, now, key, undoSetRefreshRecord, filter, data, current, maxUpdateAt_1, ids, filter_2, _a, ids, count, aggr, selection2, data, err_1;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
canOptimize = this.canOptimizeRefresh(entity, selection, option, getCount);
now = 0, key = '';
if (canOptimize) {
filter = selection.filter;
(0, assert_1.default)(filter);
key = this.filterToKey(filter);
now = Date.now();
if (this.refreshRecords[entity] && this.refreshRecords[entity][key]) {
if (this.refreshRecords[entity][key] > now - this.keepFreshPeriod && this.savedEntities.includes(entity)) {
// 对于savedEntities同一查询条件不必过于频繁刷新减少性能开销
if (process.env.NODE_ENV === 'development') {
// console.warn('根据keepFresh规则省略了一次请求数据的行为', entity, selection);
}
data = this.get(entity, selection);
return [2 /*return*/, {
data: data,
}];
}
else {
// 对于其它entity或者是过期的savedEntity可以加上增量条件只检查上次查询之后有更新的数据减少网络传输开销
if (!this.savedEntities.includes(entity) && process.env.NODE_ENV === 'development') {
console.log('对象的查询可能可以被缓存,请查看代码逻辑', entity);
}
selection.filter = (0, filter_1.combineFilters)(entity, this.getSchema(), [selection.filter, {
$$updateAt$$: {
$gte: this.refreshRecords[entity][key]
}
}]);
}
}
else if (this.savedEntities.includes(entity)) {
current = this.get(entity, {
data: {
id: 1,
$$updateAt$$: 1,
},
filter: filter,
});
if (current.length > 0) {
maxUpdateAt_1 = 0;
ids = current.map(function (row) {
if (typeof row.$$updateAt$$ === 'number' && row.$$updateAt$$ > maxUpdateAt_1) {
maxUpdateAt_1 = row.$$updateAt$$;
}
return row.id;
});
filter_2 = {
$or: [
{
id: {
$nin: ids,
},
},
{
$$updateAt$$: {
$gte: maxUpdateAt_1,
},
}
],
};
selection.filter = (0, filter_1.combineFilters)(entity, this.getSchema(), [filter_2, selection.filter]);
}
}
undoSetRefreshRecord = this.addRefreshRecord(entity, key, now);
}
this.refreshing = true;
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.exec('select', {
entity: entity,
selection: selection,
option: option,
getCount: getCount,
}, callback, dontPublish)];
case 1:
case 2:
_a = (_b.sent()).result, ids = _a.ids, count = _a.count, aggr = _a.aggr;
if (canOptimize) {
}
selection2 = Object.assign({}, selection, {
filter: {
id: {
@ -106,6 +340,12 @@ var Cache = /** @class */ (function (_super) {
data: data,
count: count,
}];
case 3:
err_1 = _b.sent();
this.refreshing = false;
undoSetRefreshRecord && undoSetRefreshRecord();
throw err_1;
case 4: return [2 /*return*/];
}
});
});
@ -177,7 +417,7 @@ var Cache = /** @class */ (function (_super) {
* @returns
*/
Cache.prototype.tryRedoOperations = function (operations) {
var e_2, _a;
var e_4, _a;
var context = this.contextBuilder();
context.begin();
try {
@ -192,12 +432,12 @@ var Cache = /** @class */ (function (_super) {
});
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (operations_1_1 && !operations_1_1.done && (_a = operations_1.return)) _a.call(operations_1);
}
finally { if (e_2) throw e_2.error; }
finally { if (e_4) throw e_4.error; }
}
context.rollback();
return true;
@ -260,7 +500,7 @@ var Cache = /** @class */ (function (_super) {
this.refreshing = true;
this.exec('fetchRows', missedRows_1, function (result, opRecords) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var _a, _b, record, d, missedRows_2, missedRows_2_1, mr;
var e_3, _c, e_4, _d;
var e_5, _c, e_6, _d;
return tslib_1.__generator(this, function (_e) {
try {
// missedRows理论上一定要取到不能为空集。否则就是程序员有遗漏
@ -269,26 +509,26 @@ var Cache = /** @class */ (function (_super) {
d = record.d;
(0, assert_1.default)(Object.keys(d).length > 0, '在通过fetchRow取不一致数据时返回了空数据请拿该程序员祭天。');
try {
for (missedRows_2 = (e_4 = void 0, tslib_1.__values(missedRows_1)), missedRows_2_1 = missedRows_2.next(); !missedRows_2_1.done; missedRows_2_1 = missedRows_2.next()) {
for (missedRows_2 = (e_6 = void 0, tslib_1.__values(missedRows_1)), missedRows_2_1 = missedRows_2.next(); !missedRows_2_1.done; missedRows_2_1 = missedRows_2.next()) {
mr = missedRows_2_1.value;
(0, assert_1.default)(Object.keys(d[mr.entity]).length > 0, "\u5728\u901A\u8FC7fetchRow\u53D6\u4E0D\u4E00\u81F4\u6570\u636E\u65F6\u8FD4\u56DE\u4E86\u7A7A\u6570\u636E\uFF0C\u8BF7\u62FF\u8BE5\u7A0B\u5E8F\u5458\u796D\u5929\u3002entity\u662F".concat(mr.entity));
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (missedRows_2_1 && !missedRows_2_1.done && (_d = missedRows_2.return)) _d.call(missedRows_2);
}
finally { if (e_4) throw e_4.error; }
finally { if (e_6) throw e_6.error; }
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
}
finally { if (e_3) throw e_3.error; }
finally { if (e_5) throw e_5.error; }
}
return [2 /*return*/];
});

View File

@ -1,4 +1,4 @@
import { Aspect, AspectWrapper, AuthCascadePath, AuthDeduceRelationMap, EntityDict } from 'oak-domain/lib/types';
import { Aspect, AspectWrapper, AuthCascadePath, AuthDeduceRelationMap, Checker, EntityDict } from 'oak-domain/lib/types';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { ColorDict } from 'oak-domain/lib/types/Style';
import { CommonAspectDict } from 'oak-common-aspect';
@ -21,7 +21,7 @@ import { ContextMenuFactory } from './contextMenuFactory';
import { Geo } from './geo';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
export declare function initializeStep2<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>>(features: Pick<BasicFeatures<ED, Cxt, FrontCxt, AD>, 'localStorage' | 'environment'>, aspectWrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, storageSchema: StorageSchema<ED>, contextBuilder: () => FrontCxt, store: CacheStore<ED, FrontCxt>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities: (keyof ED)[], createFreeEntities: (keyof ED)[], updateFreeEntities: (keyof ED)[], colorDict: ColorDict<ED>, getFullDataFn: () => any, makeBridgeUrlFn?: (url: string, headers?: Record<string, string>) => string): {
export declare function initializeStep2<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>>(features: Pick<BasicFeatures<ED, Cxt, FrontCxt, AD>, 'localStorage' | 'environment'>, aspectWrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, storageSchema: StorageSchema<ED>, frontendContextBuilder: () => (store: CacheStore<ED, FrontCxt>) => FrontCxt, checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, colorDict: ColorDict<ED>, getFullDataFn: () => any, makeBridgeUrlFn?: (url: string, headers?: Record<string, string>) => string, selectFreeEntities?: (keyof ED)[], createFreeEntities?: (keyof ED)[], updateFreeEntities?: (keyof ED)[], savedEntities?: (keyof ED)[], keepFreshPeriod?: number): {
cache: Cache<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>;
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>;
runningTree: RunningTree<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>;

View File

@ -16,10 +16,10 @@ var relationAuth_1 = require("./relationAuth");
var style_1 = require("./style");
var contextMenuFactory_1 = require("./contextMenuFactory");
var geo_1 = require("./geo");
function initializeStep2(features, aspectWrapper, storageSchema, contextBuilder, store, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities, colorDict, getFullDataFn, makeBridgeUrlFn) {
function initializeStep2(features, aspectWrapper, storageSchema, frontendContextBuilder, checkers, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, colorDict, getFullDataFn, makeBridgeUrlFn, selectFreeEntities, createFreeEntities, updateFreeEntities, savedEntities, keepFreshPeriod) {
var localStorage = features.localStorage, environment = features.environment;
var cache = new cache_1.Cache(aspectWrapper, contextBuilder, store, getFullDataFn);
var relationAuth = new relationAuth_1.RelationAuth(contextBuilder, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities);
var cache = new cache_1.Cache(storageSchema, aspectWrapper, frontendContextBuilder, checkers, getFullDataFn, localStorage, savedEntities, keepFreshPeriod);
var relationAuth = new relationAuth_1.RelationAuth(cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities);
var runningTree = new runningTree_1.RunningTree(cache, storageSchema, relationAuth);
var geo = new geo_1.Geo(aspectWrapper);
var port = new port_1.Port(aspectWrapper);

View File

@ -17,12 +17,11 @@ export declare class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends
private language;
private defaultLng;
private i18n;
private loadingRecord;
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, localStorage: LocalStorage, environment: Environment, defaultLng: string, makeBridgeUrlFn?: (url: string, headers?: Record<string, string>) => string);
private detectLanguange;
private resetDataset;
/**
* key缺失时i18n数据
* key缺失时i18n数据i18n缓存数据的行为优化放在cache中统一进行
* @param ns
*/
private loadData;

View File

@ -5,13 +5,11 @@ var tslib_1 = require("tslib");
var Feature_1 = require("../types/Feature");
var assert_1 = tslib_1.__importDefault(require("assert"));
var i18n_js_1 = require("i18n-js");
var lodash_1 = require("oak-domain/lib/utils/lodash");
var constant_1 = require("../constant/constant");
var Locales = /** @class */ (function (_super) {
tslib_1.__extends(Locales, _super);
function Locales(cache, localStorage, environment, defaultLng, makeBridgeUrlFn) {
var _this = _super.call(this) || this;
_this.loadingRecord = {};
_this.cache = cache;
_this.localStorage = localStorage;
_this.defaultLng = defaultLng;
@ -78,48 +76,17 @@ var Locales = /** @class */ (function (_super) {
this.i18n.store(dataset);
};
/**
* 当发生key缺失时向服务器请求最新的i18n数据这里要注意要避免因服务器也缺失导致的无限请求
* 当发生key缺失时向服务器请求最新的i18n数据对i18n缓存数据的行为优化放在cache中统一进行
* @param ns
*/
Locales.prototype.loadData = function (key) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _a, ns, currentI18ns, filters, now, newI18ns, dataset_1;
var _a, ns, newI18ns, dataset_1;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
(0, assert_1.default)(typeof key === 'string');
_a = tslib_1.__read(key.split('.'), 1), ns = _a[0];
currentI18ns = this.cache.get('i18n', {
data: {
id: 1,
$$updateAt$$: 1,
},
filter: {
namespace: ns,
language: {
$in: [this.language, this.defaultLng].filter(function (ele) { return !!ele; })
},
}
});
filters = (0, lodash_1.uniq)([this.language, this.defaultLng]).map(function (ele) {
var current = currentI18ns.find(function (ele2) { return ele2.language === ele; });
if (current) {
return {
language: ele,
$$updateAt$$: {
$gt: current.$$updateAt$$,
},
};
}
return {
language: ele,
};
});
now = Date.now();
if (this.loadingRecord[ns] && now - this.loadingRecord[ns] < Locales.MINIMAL_LOADING_GAP) {
return [2 /*return*/];
}
this.loadingRecord[ns] = now;
return [4 /*yield*/, this.cache.refresh('i18n', {
data: {
id: 1,
@ -131,7 +98,6 @@ var Locales = /** @class */ (function (_super) {
},
filter: {
namespace: ns,
$or: filters,
}
}, undefined, undefined, undefined, true)];
case 1:

View File

@ -7,7 +7,6 @@ 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 actionCascadePathGraph;
private actionCascadePathMap;
private relationCascadePathGraph;
@ -15,7 +14,7 @@ export declare class RelationAuth<ED extends EntityDict & BaseEntityDict, Cxt ex
private authDeduceRelationMap;
static IgnoredActions: string[];
private entityGraph?;
constructor(contextBuilder: () => FrontCxt, cache: Cache<ED, Cxt, FrontCxt, AD>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities: (keyof ED)[], createFreeEntities: (keyof ED)[], updateFreeEntities: (keyof ED)[]);
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: (keyof ED)[], createFreeEntities?: (keyof ED)[], updateFreeEntities?: (keyof ED)[]);
private judgeRelation;
getHasRelationEntities(): string[];
getDeduceRelationAttribute(entity: keyof ED): string | undefined;

View File

@ -9,9 +9,8 @@ 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(contextBuilder, cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities) {
function RelationAuth(cache, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities) {
var _this = _super.call(this) || this;
_this.contextBuilder = contextBuilder;
_this.cache = cache;
_this.actionCascadePathGraph = actionCascadePathGraph;
_this.relationCascadePathGraph = relationCascadePathGraph;
@ -175,7 +174,7 @@ var RelationAuth = /** @class */ (function (_super) {
return relationAuths;
};
RelationAuth.prototype.checkRelation = function (entity, operation) {
var context = this.contextBuilder();
var context = this.cache.begin();
context.begin();
try {
this.baseRelationAuth.checkRelationSync(entity, operation, context);

View File

@ -8,7 +8,6 @@ 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 oak_common_aspect_2 = require("oak-common-aspect");
var CacheStore_1 = require("./cacheStore/CacheStore");
/**
* @param storageSchema
* @param createFeatures
@ -24,7 +23,7 @@ var CacheStore_1 = require("./cacheStore/CacheStore");
*/
function initialize(storageSchema, frontendContextBuilder, backendContextBuilder, aspectDict, triggers, checkers, watchers, timers, startRoutines, initialData, option) {
var _this = this;
var actionCascadePathGraph = option.actionCascadePathGraph, actionDict = option.actionDict, relationCascadePathGraph = option.relationCascadePathGraph, authDeduceRelationMap = option.authDeduceRelationMap, colorDict = option.colorDict, importations = option.importations, exportations = option.exportations, selectFreeEntities = option.selectFreeEntities, createFreeEntities = option.createFreeEntities, updateFreeEntities = option.updateFreeEntities;
var actionCascadePathGraph = option.actionCascadePathGraph, actionDict = option.actionDict, relationCascadePathGraph = option.relationCascadePathGraph, authDeduceRelationMap = option.authDeduceRelationMap, colorDict = option.colorDict, importations = option.importations, exportations = option.exportations, selectFreeEntities = option.selectFreeEntities, createFreeEntities = option.createFreeEntities, updateFreeEntities = option.updateFreeEntities, cacheKeepFreshPeriod = option.cacheKeepFreshPeriod, cacheSavedEntities = option.cacheSavedEntities;
var intersected = (0, lodash_1.intersection)(Object.keys(oak_common_aspect_1.default), Object.keys(aspectDict));
if (intersected.length > 0) {
throw new Error("\u7528\u6237\u5B9A\u4E49\u7684aspect\u4E2D\u4E0D\u80FD\u548C\u7CFB\u7EDFaspect\u540C\u540D\uFF1A\u300C".concat(intersected.join(','), "\u300D"));
@ -35,15 +34,14 @@ function initialize(storageSchema, frontendContextBuilder, backendContextBuilder
var triggers2 = triggers.concat(intTriggers);
var watchers2 = watchers.concat(intWatchers);
var features1 = (0, features_1.initializeStep1)();
var debugStore = (0, debugStore_1.createDebugStore)(storageSchema, backendContextBuilder, triggers2, checkers2, watchers2, timers, startRoutines, initialData, actionDict, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities, function (key, data) { return features1.localStorage.save(key, data); }, function (key) { return features1.localStorage.load(key); });
var cacheStore = new CacheStore_1.CacheStore(storageSchema);
var debugStore = (0, debugStore_1.createDebugStore)(storageSchema, backendContextBuilder, triggers2, checkers2, watchers2, timers, startRoutines, initialData, actionDict, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, function (key, data) { return features1.localStorage.save(key, data); }, function (key) { return features1.localStorage.load(key); }, selectFreeEntities, createFreeEntities, updateFreeEntities);
var wrapper = {
exec: function (name, params) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var context, str, contextBackend, result, err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
context = frontendContextBuilder()(cacheStore);
context = features2.cache.begin();
str = context.toString();
return [4 /*yield*/, backendContextBuilder(str)(debugStore)];
case 1:
@ -76,8 +74,7 @@ function initialize(storageSchema, frontendContextBuilder, backendContextBuilder
});
}); },
};
var features2 = (0, features_1.initializeStep2)(features1, wrapper, storageSchema, function () { return frontendContextBuilder()(cacheStore); }, cacheStore, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities, colorDict, function () { return debugStore.getCurrentData(); });
checkers2.forEach(function (checker) { return cacheStore.registerChecker(checker); });
var features2 = (0, features_1.initializeStep2)(features1, wrapper, storageSchema, frontendContextBuilder, checkers2, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, colorDict, function () { return debugStore.getCurrentData(); }, undefined, selectFreeEntities, createFreeEntities, updateFreeEntities, cacheSavedEntities, cacheKeepFreshPeriod);
(0, oak_common_aspect_2.registerPorts)(importations || [], exportations || []);
var features = Object.assign(features2, features1);
return {

View File

@ -4,7 +4,6 @@ exports.initialize = void 0;
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");
/**
* @param storageSchema
* @param createFeatures
@ -20,18 +19,17 @@ var CacheStore_1 = require("./cacheStore/CacheStore");
*/
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, createFreeEntities = option.createFreeEntities, updateFreeEntities = option.updateFreeEntities, colorDict = option.colorDict;
var actionCascadePathGraph = option.actionCascadePathGraph, relationCascadePathGraph = option.relationCascadePathGraph, authDeduceRelationMap = option.authDeduceRelationMap, actionDict = option.actionDict, selectFreeEntities = option.selectFreeEntities, createFreeEntities = option.createFreeEntities, updateFreeEntities = option.updateFreeEntities, colorDict = option.colorDict, cacheKeepFreshPeriod = option.cacheKeepFreshPeriod, cacheSavedEntities = option.cacheSavedEntities;
var intCheckers = (0, actionDef_1.makeIntrinsicCTWs)(storageSchema, actionDict).checkers;
var checkers2 = checkers.concat(intCheckers);
var features1 = (0, features_1.initializeStep1)();
var cacheStore = new CacheStore_1.CacheStore(storageSchema);
var wrapper = {
exec: function (name, params) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var context, _a, result, opRecords, message;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
context = frontendContextBuilder()(cacheStore);
context = features2.cache.begin();
return [4 /*yield*/, connector.callAspect(name, params, context)];
case 1:
_a = _b.sent(), result = _a.result, opRecords = _a.opRecords, message = _a.message;
@ -44,8 +42,7 @@ function initialize(storageSchema, frontendContextBuilder, connector, checkers,
});
}); },
};
var features2 = (0, features_1.initializeStep2)(features1, wrapper, storageSchema, function () { return frontendContextBuilder()(cacheStore); }, cacheStore, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities, colorDict, function () { return '请查看数据库中的数据'; }, function (url, headers) { return connector.makeBridgeUrl(url, headers); });
checkers2.forEach(function (checker) { return cacheStore.registerChecker(checker); });
var features2 = (0, features_1.initializeStep2)(features1, wrapper, storageSchema, frontendContextBuilder, checkers2, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, colorDict, function () { return '请查看数据库中的数据'; }, function (url, headers) { return connector.makeBridgeUrl(url, headers); }, selectFreeEntities, createFreeEntities, updateFreeEntities, cacheSavedEntities, cacheKeepFreshPeriod);
var features = Object.assign(features1, features2);
return {
features: features,

View File

@ -166,7 +166,10 @@ function checkActionsAndCascadeEntities(rows, option) {
var checkTypes = ['relation', 'row', 'logical', 'logicalRelation'];
var actions = this.props.oakActions ? JSON.parse(this.props.oakActions) : (typeof option.actions === 'function' ? option.actions.call(this) : option.actions);
var legalActions = [];
// 这里向服务器请求相应的actionAuthcache层会对请求加以优化避免反复过频的不必要取数据
var destEntities = [];
if (actions) {
destEntities.push(this.state.oakEntity);
var _loop_3 = function (action) {
if (rows instanceof Array) {
(0, assert_1.assert)(option.isList);
@ -290,6 +293,7 @@ function checkActionsAndCascadeEntities(rows, option) {
if (cascadeActions) {
var rel_1 = (0, relation_1.judgeRelation)(this_2.features.cache.getSchema(), this_2.state.oakEntity, e);
(0, assert_1.assert)(rel_1 instanceof Array, "".concat(this_2.state.oakFullpath, "\u4E0A\u6240\u5B9A\u4E49\u7684cascadeAction\u4E2D\u7684\u952E\u503C").concat(e, "\u4E0D\u662F\u4E00\u5BF9\u591A\u6620\u5C04"));
destEntities.push(rel_1[0]);
var _loop_5 = function (action) {
var _h, _j, _k, _l;
if (rows instanceof Array) {
@ -389,6 +393,23 @@ function checkActionsAndCascadeEntities(rows, option) {
_loop_4(e);
}
}
if (destEntities.length > 0) {
// 权限判断需要actionAuth的数据这里向cache请求时会根据keepFresh规则进行一定程度的优化。
this.features.cache.refresh('actionAuth', {
data: {
id: 1,
relationId: 1,
paths: 1,
destEntity: 1,
deActions: 1,
},
filter: {
destEntity: {
$in: destEntities,
}
}
});
}
return legalActions;
}
function reRender(option, extra) {

View File

@ -9,7 +9,9 @@ export declare type InitializeOptions<ED extends EntityDict & BaseEntityDict> =
colorDict: ColorDict<ED>;
importations?: Importation<ED, keyof ED, any>[];
exportations?: Exportation<ED, keyof ED, any>[];
selectFreeEntities: (keyof ED)[];
createFreeEntities: (keyof ED)[];
updateFreeEntities: (keyof ED)[];
selectFreeEntities?: (keyof ED)[];
createFreeEntities?: (keyof ED)[];
updateFreeEntities?: (keyof ED)[];
cacheSavedEntities?: (keyof ED)[];
cacheKeepFreshPeriod?: number;
};

View File

@ -17,18 +17,12 @@ export class CacheStore<
Cxt extends SyncContext<ED>
> extends TreeStore<ED> implements SyncRowStore<ED, SyncContext<ED>>{
private triggerExecutor: SyncTriggerExecutor<ED, Cxt>;
private savedEntities: (keyof ED)[];
private keepFreshPeriod: number;
constructor(
storageSchema: StorageSchema<ED>,
keepFreshPeriod: number,
savedEntities: (keyof ED)[],
) {
super(storageSchema);
this.triggerExecutor = new SyncTriggerExecutor();
this.keepFreshPeriod = keepFreshPeriod;
this.savedEntities = savedEntities;
}
aggregate<T extends keyof ED, OP extends SelectOption>(entity: T, aggregation: ED[T]['Aggregation'], context: SyncContext<ED>, option: OP): AggregationResult<ED[T]['Schema']> {

View File

@ -1,5 +1,7 @@
export const LOCAL_STORAGE_KEYS = {
debugStore: 'oak-fronted-base:debugStore',
debugStoreStat: 'oak-fronted-base:debugStoreStat',
localeLng: 'oak-frontend-base:feature-locale-lng',
debugStore: 'ofd:ds',
debugStoreStat: 'ofd:dss',
localeLng: 'ofd:f-l-l',
cacheSaved: 'ofd:f-c-s',
cacheRefreshRecord: 'ofd:f-c-rr',
};

View File

@ -25,9 +25,9 @@ export class DebugStore<ED extends EntityDict & BaseEntityDict, Cxt extends Asyn
actionCascadeGraph: AuthCascadePath<ED>[],
relationCascadeGraph: AuthCascadePath<ED>[],
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
selectFreeEntities: (keyof ED)[],
createFreeEntities: (keyof ED)[],
updateFreeEntities: (keyof ED)[]
selectFreeEntities?: (keyof ED)[],
createFreeEntities?: (keyof ED)[],
updateFreeEntities?: (keyof ED)[]
) {
super(storageSchema);
this.executor = new TriggerExecutor((cxtString) => contextBuilder(cxtString)(this));

View File

@ -215,11 +215,11 @@ export function createDebugStore<ED extends EntityDict & BaseEntityDict, Cxt ext
actionCascadePathGraph: AuthCascadePath<ED>[],
relationCascadePathGraph: AuthCascadePath<ED>[],
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
selectFreeEntities: (keyof ED)[],
createFreeEntities: (keyof ED)[],
updateFreeEntities: (keyof ED)[],
saveFn: (key: string, data: any) => void,
loadFn: (key: string) => any) {
loadFn: (key: string) => any,
selectFreeEntities?: (keyof ED)[],
createFreeEntities?: (keyof ED)[],
updateFreeEntities?: (keyof ED)[],) {
const store = new DebugStore<ED, Cxt>(storageSchema, contextBuilder, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap,
selectFreeEntities, createFreeEntities, updateFreeEntities);

View File

@ -1,21 +1,30 @@
import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, CheckerType, Aspect, SelectOpResult } from 'oak-domain/lib/types';
import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, CheckerType, Aspect, SelectOpResult, StorageSchema, Checker, EXPRESSION_PREFIX } 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';
import { merge, pull } from 'oak-domain/lib/utils/lodash';
import { merge, pull, intersection, omit, pick } from 'oak-domain/lib/utils/lodash';
import { CacheStore } from '../cacheStore/CacheStore';
import { OakRowUnexistedException, OakRowInconsistencyException, OakException, OakUserException } from 'oak-domain/lib/types/Exception';
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';
import { LocalStorage } from './localStorage';
import { LOCAL_STORAGE_KEYS } from '../constant/constant';
import { combineFilters } from 'oak-domain/lib/store/filter';
const DEFAULT_KEEP_FRESH_PERIOD = 600 * 1000; // 10分钟不刷新
interface CacheSelectOption extends SelectOption {
ignoreKeepFreshRule?: true,
};
export class Cache<
ED extends EntityDict & BaseEntityDict,
Cxt extends AsyncContext<ED>,
FrontCxt extends SyncContext<ED>,
AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>
> extends Feature {
> extends Feature {
cacheStore: CacheStore<ED, FrontCxt>;
private aspectWrapper: AspectWrapper<ED, Cxt, AD>;
private syncEventsCallbacks: Array<
@ -23,35 +32,73 @@ export class Cache<
>;
private contextBuilder?: () => FrontCxt;
private refreshing = false;
private savedEntities: (keyof ED)[];
private keepFreshPeriod: number;
private localStorage: LocalStorage;
private getFullDataFn: () => any;
private refreshRecords: {
[T in keyof ED]?: Record<string, number>;
} = {};
constructor(
storageSchema: StorageSchema<ED>,
aspectWrapper: AspectWrapper<ED, Cxt, AD>,
contextBuilder: () => FrontCxt,
store: CacheStore<ED, FrontCxt>,
getFullData: () => any
frontendContextBuilder: () => (store: CacheStore<ED, FrontCxt>) => FrontCxt,
checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>,
getFullData: () => any,
localStorage: LocalStorage,
savedEntities?: (keyof ED)[],
keepFreshPeriod?: number,
) {
super();
this.aspectWrapper = aspectWrapper;
this.syncEventsCallbacks = [];
this.contextBuilder = contextBuilder;
this.cacheStore = store;
this.getFullDataFn = getFullData;
this.cacheStore = new CacheStore(storageSchema);
this.contextBuilder = () => frontendContextBuilder()(this.cacheStore);
this.savedEntities = ['actionAuth', 'i18n', ...(savedEntities || [])];
this.keepFreshPeriod = keepFreshPeriod || DEFAULT_KEEP_FRESH_PERIOD;
this.localStorage = localStorage;
// 在这里把wrapper的返回opRecords截取到并同步到cache中
/* const { exec } = aspectWrapper;
aspectWrapper.exec = async <T extends keyof AD>(
name: T,
params: any
) => {
const { result, opRecords } = await exec(name, params);
this.sync(opRecords);
return {
result,
opRecords,
};
}; */
checkers.forEach(
(checker) => this.cacheStore.registerChecker(checker)
);
this.getFullDataFn = getFullData;
this.initSavedLogic();
}
/**
* cache中需要缓存的数据
*/
private initSavedLogic() {
const data: {
[T in keyof ED]?: ED[T]['OpSchema'][];
} = {};
this.savedEntities.forEach(
(entity) => {
const key = `${LOCAL_STORAGE_KEYS.cacheSaved}:${entity as string}`;
const cached = this.localStorage.load(key);
if (cached) {
data[entity] = cached;
}
}
);
this.cacheStore.resetInitialData(data);
this.cacheStore.onCommit((result) => {
const entities = Object.keys(result);
const referenced = intersection(entities, this.savedEntities);
if (referenced.length > 0) {
const saved = this.cacheStore.getCurrentData(referenced);
Object.keys(saved).forEach(
(entity) => {
const key = `${LOCAL_STORAGE_KEYS.cacheSaved}:${entity as string}`;
this.localStorage.save(key, saved[entity]);
}
)
}
});
}
getSchema() {
@ -97,7 +144,141 @@ export class Cache<
}
}
async refresh<T extends keyof ED, OP extends SelectOption>(
private getRefreshRecordSize() {
let count = 0;
Object.keys(this.refreshRecords).forEach(
(entity) => count += Object.keys(this.refreshRecords[entity]!).length
);
return count;
}
private reduceRefreshRecord(now: number) {
Object.keys(this.refreshRecords).forEach(
(ele) => {
if (!this.savedEntities.includes(ele)) {
const outdated: string[] = [];
for (const filter in this.refreshRecords[ele]) {
if (this.refreshRecords[ele]![filter]! < now - this.keepFreshPeriod) {
outdated.push(filter);
}
}
this.refreshRecords[ele as keyof ED] = omit(this.refreshRecords[ele], outdated);
}
}
);
}
private addRefreshRecord(entity: keyof ED, filter: string, now: number) {
const originTimestamp = this.refreshRecords[entity] && this.refreshRecords[entity]![filter];
if (this.refreshRecords[entity]) {
Object.assign(this.refreshRecords[entity]!, {
[filter]: now,
});
}
else {
Object.assign(this.refreshRecords, {
[entity]: {
[filter]: now,
}
});
}
let count = this.getRefreshRecordSize();
if (count > 100) {
count = this.getRefreshRecordSize();
this.reduceRefreshRecord(now);
if (count > 100) {
console.warn('cache中的refreshRecord数量过多请检查是否因为存在带有Date.now等变量的刷新例程', this.refreshRecords);
}
}
if (originTimestamp) {
return () => this.addRefreshRecord(entity, filter, originTimestamp);
}
}
/**
* refresh行为是否可以应用缓存优化
* selection必须满足
* 1indexFrom和count
* 2getCount
* 3projection和filter只限定在该对象自身属性上
* 4filter
* @param entity
* @param selection
* @param option
* @param getCount
*/
private canOptimizeRefresh<T extends keyof ED, OP extends CacheSelectOption>(
entity: T,
selection: ED[T]['Selection'],
option?: OP,
getCount?: true): boolean {
if (getCount || selection.indexFrom || selection.count || selection.randomRange || !selection.filter || option?.ignoreKeepFreshRule) {
return false;
}
const { data, filter, sorter } = selection;
// projection中不能有cascade查询或者表达式
const checkProjection = (projection: ED[keyof ED]['Selection']['data']) => {
for (const attr in data) {
const rel = this.judgeRelation(entity, attr);
if (typeof rel !== 'number' || ![0, 1].includes(rel)) {
return false;
}
}
return true;
};
if (!checkProjection(data)) {
return false;
}
// filter中不能有cascade查询或者表达式
const checkFilter = (filter2: ED[keyof ED]['Selection']['filter']): boolean => {
for (const attr in filter2) {
if (['$and', '$or'].includes(attr)) {
for (const f2 of filter2[attr]) {
if (!checkFilter(f2)) {
return false;
}
}
}
else if (attr === '$not') {
if (!checkFilter(filter2[attr])) {
return false;
}
}
else if (!attr.startsWith('$') || !attr.startsWith('#')) {
const rel = this.judgeRelation(entity, attr);
if (typeof rel !== 'number' || ![0, 1].includes(rel)) {
return false;
}
}
}
return true;
};
if (!checkFilter(filter)) {
return false;
}
if (sorter) {
for (const s of sorter) {
if (!checkProjection(s.$attr)) {
return false;
}
}
}
return true;
}
private filterToKey(filter: object) {
return JSON.stringify(filter);
}
async refresh<T extends keyof ED, OP extends CacheSelectOption>(
entity: T,
selection: ED[T]['Selection'],
option?: OP,
@ -105,29 +286,118 @@ export class Cache<
callback?: (result: Awaited<ReturnType<AD['select']>>) => void,
dontPublish?: true
) {
this.refreshing = true;
const { result: { ids, count, aggr } } = await this.exec('select', {
entity,
selection,
option,
getCount,
}, callback, dontPublish);
// todo 还要判定没有aggregation
const canOptimize = this.canOptimizeRefresh(entity, selection, option, getCount);
let now = 0, key = '';
let undoSetRefreshRecord: undefined | (() => void);
if (canOptimize) {
const { filter } = selection;
assert(filter);
key = this.filterToKey(filter);
const selection2 = Object.assign({}, selection, {
filter: {
id: {
$in: ids,
now = Date.now();
if (this.refreshRecords[entity] && this.refreshRecords[entity]![key]) {
if (this.refreshRecords[entity]![key] > now - this.keepFreshPeriod && this.savedEntities.includes(entity)) {
// 对于savedEntities同一查询条件不必过于频繁刷新减少性能开销
if (process.env.NODE_ENV === 'development') {
// console.warn('根据keepFresh规则省略了一次请求数据的行为', entity, selection);
}
const data = this.get(entity, selection);
return {
data,
};
}
else {
// 对于其它entity或者是过期的savedEntity可以加上增量条件只检查上次查询之后有更新的数据减少网络传输开销
if (!this.savedEntities.includes(entity) && process.env.NODE_ENV === 'development') {
console.log('对象的查询可能可以被缓存,请查看代码逻辑', entity);
}
selection.filter = combineFilters(entity, this.getSchema(), [selection.filter, {
$$updateAt$$: {
$gte: this.refreshRecords[entity]![key]!
}
}]);
}
}
});
const data = this.get(entity, selection2);
if (aggr) {
merge(data, aggr);
else if (this.savedEntities.includes(entity)) {
// 启动以后的第一次查询因为此entity会被缓存因此可以利用满足查询条件的行上的$$updateAt$$作为上一次查询的时间戳,来最大限度利用缓存减少网络传输开销
const current = this.get(entity, {
data: {
id: 1,
$$updateAt$$: 1,
},
filter,
});
if (current.length > 0) {
let maxUpdateAt = 0;
const ids = current.map(
(row) => {
if (typeof row.$$updateAt$$ === 'number' && row.$$updateAt$$ > maxUpdateAt) {
maxUpdateAt = row.$$updateAt$$;
}
return row.id!;
}
);
/**
* filter其实有疏漏updateAt在它自己的updateAt和maxUpdateAt之间就会得不到更新
* savedEntites这一概率是极低的
*/
const filter: ED[T]['Selection']['filter'] = {
$or: [
{
id: {
$nin: ids,
},
},
{
$$updateAt$$: {
$gte: maxUpdateAt,
},
}
],
};
selection.filter = combineFilters(entity, this.getSchema(), [filter, selection.filter]);
}
}
undoSetRefreshRecord = this.addRefreshRecord(entity, key, now);
}
this.refreshing = true;
try {
const { result: { ids, count, aggr } } = await this.exec('select', {
entity,
selection,
option,
getCount,
}, callback, dontPublish);
if (canOptimize) {
}
const selection2 = Object.assign({}, selection, {
filter: {
id: {
$in: ids,
}
}
});
const data = this.get(entity, selection2);
if (aggr) {
merge(data, aggr);
}
return {
data: data as Partial<ED[T]['Schema']>[],
count,
};
}
catch(err) {
this.refreshing = false;
undoSetRefreshRecord && undoSetRefreshRecord();
throw err;
}
return {
data: data as Partial<ED[T]['Schema']>[],
count,
};
}
@ -297,7 +567,7 @@ export class Cache<
context?: FrontCxt,
allowMiss?: boolean
) {
const context2 = context || this.contextBuilder!();
const context2 = context || this.contextBuilder!();
return this.getInner(entity, selection, context2, allowMiss);
}

View File

@ -1,4 +1,4 @@
import { Aspect, AspectWrapper, AuthCascadePath, AuthDeduceRelationMap, EntityDict } from 'oak-domain/lib/types';
import { Aspect, AspectWrapper, AuthCascadePath, AuthDeduceRelationMap, Checker, EntityDict } from 'oak-domain/lib/types';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { ColorDict } from 'oak-domain/lib/types/Style';
@ -27,20 +27,23 @@ export function initializeStep2<ED extends EntityDict & BaseEntityDict, Cxt exte
features: Pick<BasicFeatures<ED, Cxt, FrontCxt, AD>, 'localStorage' | 'environment'>,
aspectWrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>,
storageSchema: StorageSchema<ED>,
contextBuilder: () => FrontCxt,
store: CacheStore<ED, FrontCxt>,
frontendContextBuilder: () => (store: CacheStore<ED, FrontCxt>) => FrontCxt,
checkers: Array<Checker<ED, keyof ED, FrontCxt | Cxt>>,
actionCascadePathGraph: AuthCascadePath<ED>[],
relationCascadePathGraph: AuthCascadePath<ED>[],
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
selectFreeEntities: (keyof ED)[],
createFreeEntities: (keyof ED)[],
updateFreeEntities: (keyof ED)[],
colorDict: ColorDict<ED>,
getFullDataFn: () => any,
makeBridgeUrlFn?: (url: string, headers?: Record<string, string>) => string) {
makeBridgeUrlFn?: (url: string, headers?: Record<string, string>) => string,
selectFreeEntities?: (keyof ED)[],
createFreeEntities?: (keyof ED)[],
updateFreeEntities?: (keyof ED)[],
savedEntities?: (keyof ED)[],
keepFreshPeriod?: number) {
const { localStorage, environment } = features;
const cache = new Cache<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper, contextBuilder, store, getFullDataFn);
const relationAuth = new RelationAuth<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(contextBuilder, cache,
const cache = new Cache<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(storageSchema, aspectWrapper,
frontendContextBuilder, checkers, getFullDataFn, localStorage, savedEntities, keepFreshPeriod);
const relationAuth = new RelationAuth<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(cache,
actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities, createFreeEntities, updateFreeEntities);
const runningTree = new RunningTree<ED, Cxt, FrontCxt, AD & CommonAspectDict<ED, Cxt>>(cache, storageSchema, relationAuth);
const geo = new Geo(aspectWrapper);

View File

@ -22,7 +22,6 @@ export class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncCo
private language: string;
private defaultLng: string;
private i18n: I18n;
private loadingRecord: Record<string, number> = {};
constructor(
cache: Cache<ED, Cxt, FrontCxt, AD>,
@ -93,47 +92,13 @@ export class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncCo
}
/**
* key缺失时i18n数据
* key缺失时i18n数据i18n缓存数据的行为优化放在cache中统一进行
* @param ns
*/
private async loadData(key: Scope) {
assert(typeof key === 'string');
const [ ns ] = key.split('.');
const currentI18ns = this.cache.get('i18n', {
data: {
id: 1,
$$updateAt$$: 1,
},
filter: {
namespace: ns,
language: {
$in: [this.language, this.defaultLng].filter(ele => !!ele)
},
}
});
const filters: NonNullable<EntityDict['i18n']['Selection']['filter']>[] = uniq([this.language, this.defaultLng]).map(
ele => {
const current = currentI18ns.find(ele2 => ele2.language === ele);
if (current) {
return {
language: ele,
$$updateAt$$: {
$gt: current.$$updateAt$$,
},
};
}
return {
language: ele,
};
}
);
const now = Date.now();
if (this.loadingRecord[ns] && now - this.loadingRecord[ns] < Locales.MINIMAL_LOADING_GAP) {
return;
}
this.loadingRecord[ns] = now;
const { data: newI18ns } = await this.cache.refresh('i18n', {
data: {
id: 1,
@ -145,7 +110,6 @@ export class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncCo
},
filter: {
namespace: ns,
$or: filters,
}
}, undefined, undefined, undefined, true);

View File

@ -17,7 +17,6 @@ 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 actionCascadePathGraph: AuthCascadePath<ED>[];
private actionCascadePathMap: Record<string, AuthCascadePath<ED>[]>;
private relationCascadePathGraph: AuthCascadePath<ED>[];
@ -34,17 +33,15 @@ export class RelationAuth<
};
constructor(
contextBuilder: () => FrontCxt,
cache: Cache<ED, Cxt, FrontCxt, AD>,
actionCascadePathGraph: AuthCascadePath<ED>[],
relationCascadePathGraph: AuthCascadePath<ED>[],
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
selectFreeEntities: (keyof ED)[],
createFreeEntities: (keyof ED)[],
updateFreeEntities: (keyof ED)[]
selectFreeEntities?: (keyof ED)[],
createFreeEntities?: (keyof ED)[],
updateFreeEntities?: (keyof ED)[]
) {
super();
this.contextBuilder = contextBuilder;
this.cache = cache;
this.actionCascadePathGraph = actionCascadePathGraph;
this.relationCascadePathGraph = relationCascadePathGraph;
@ -238,7 +235,7 @@ export class RelationAuth<
}
checkRelation<T extends keyof ED>(entity: T, operation: Omit< ED[T]['Operation'] | ED[T]['Selection'], 'id'>) {
const context = this.contextBuilder();
const context = this.cache.begin();
context.begin();
try {
this.baseRelationAuth.checkRelationSync(entity, operation, context);

View File

@ -42,23 +42,24 @@ export function initialize<
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: {
[T in keyof ED]?: Array<ED[T]['OpSchema']>;
},
option: InitializeOptions<ED>
) {
const { actionCascadePathGraph, actionDict, relationCascadePathGraph, authDeduceRelationMap,
colorDict, importations, exportations, selectFreeEntities, createFreeEntities, updateFreeEntities } = option;
>(
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: {
[T in keyof ED]?: Array<ED[T]['OpSchema']>;
},
option: InitializeOptions<ED>
) {
const { actionCascadePathGraph, actionDict, relationCascadePathGraph, authDeduceRelationMap,
colorDict, importations, exportations, selectFreeEntities, createFreeEntities, updateFreeEntities,
cacheKeepFreshPeriod, cacheSavedEntities } = option;
let intersected = intersection(Object.keys(commonAspectDict), Object.keys(aspectDict));
if (intersected.length > 0) {
throw new Error(
@ -86,20 +87,16 @@ export function initialize<
actionCascadePathGraph,
relationCascadePathGraph,
authDeduceRelationMap,
(key, data) => features1.localStorage.save(key, data),
(key) => features1.localStorage.load(key),
selectFreeEntities,
createFreeEntities,
updateFreeEntities,
(key, data) => features1.localStorage.save(key, data),
(key) => features1.localStorage.load(key)
);
const cacheStore = new CacheStore<ED, FrontCxt>(
storageSchema
updateFreeEntities
);
const wrapper: AspectWrapper<ED, Cxt, CommonAspectDict<ED, Cxt> & AD> = {
exec: async (name, params) => {
const context = frontendContextBuilder()(cacheStore);
const context = features2.cache.begin();
const str = context.toString();
const contextBackend = await backendContextBuilder(str)(debugStore);
await contextBackend.begin();
@ -123,18 +120,21 @@ export function initialize<
features1,
wrapper,
storageSchema,
() => frontendContextBuilder()(cacheStore),
cacheStore,
frontendContextBuilder,
checkers2,
actionCascadePathGraph,
relationCascadePathGraph,
authDeduceRelationMap,
colorDict,
() => debugStore.getCurrentData(),
undefined,
selectFreeEntities,
createFreeEntities,
updateFreeEntities,
colorDict,
() => debugStore.getCurrentData());
cacheSavedEntities,
cacheKeepFreshPeriod
);
checkers2.forEach((checker) => cacheStore.registerChecker(checker));
registerPorts(importations || [], exportations || []);

View File

@ -42,7 +42,7 @@ export function initialize<
option: InitializeOptions<ED>
) {
const { actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, actionDict,
selectFreeEntities, createFreeEntities, updateFreeEntities, colorDict } = option;
selectFreeEntities, createFreeEntities, updateFreeEntities, colorDict, cacheKeepFreshPeriod, cacheSavedEntities } = option;
const { checkers: intCheckers } = makeIntrinsicCTWs<ED, Cxt, FrontCxt>(storageSchema, actionDict);
@ -50,13 +50,9 @@ export function initialize<
const features1 = initBasicFeaturesStep1();
const cacheStore = new CacheStore<ED, FrontCxt>(
storageSchema,
);
const wrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>> = {
exec: async (name, params) => {
const context = frontendContextBuilder()(cacheStore);
const context = features2.cache.begin();
const { result, opRecords, message } = await connector.callAspect(name as string, params, context);
return {
result,
@ -70,21 +66,21 @@ export function initialize<
features1,
wrapper,
storageSchema,
() => frontendContextBuilder()(cacheStore),
cacheStore,
frontendContextBuilder,
checkers2,
actionCascadePathGraph,
relationCascadePathGraph,
authDeduceRelationMap,
colorDict,
() => '请查看数据库中的数据',
(url, headers) => connector.makeBridgeUrl(url, headers),
selectFreeEntities,
createFreeEntities,
updateFreeEntities,
colorDict,
() => '请查看数据库中的数据',
(url, headers) => connector.makeBridgeUrl(url, headers)
cacheSavedEntities,
cacheKeepFreshPeriod
);
checkers2.forEach((checker) => cacheStore.registerChecker(checker));
const features = Object.assign(features1, features2);
return {
features,

View File

@ -167,7 +167,11 @@ function checkActionsAndCascadeEntities<
const checkTypes = ['relation', 'row', 'logical', 'logicalRelation'] as CheckerType[];
const actions = this.props.oakActions ? JSON.parse(this.props.oakActions) as ED[T]['Action'][] : (typeof option.actions === 'function' ? option.actions.call(this) : option.actions);
const legalActions = [] as ActionDef<ED, T>[];
// 这里向服务器请求相应的actionAuthcache层会对请求加以优化避免反复过频的不必要取数据
const destEntities: (keyof ED)[] = [];
if (actions) {
destEntities.push(this.state.oakEntity);
// todo 这里actions整体进行测试的性能应该要高于一个个去测试
for (const action of actions as ActionDef<ED, T>[]) {
if (rows instanceof Array) {
@ -284,6 +288,7 @@ function checkActionsAndCascadeEntities<
if (cascadeActions) {
const rel = judgeRelation(this.features.cache.getSchema()!, this.state.oakEntity, e);
assert(rel instanceof Array, `${this.state.oakFullpath}上所定义的cascadeAction中的键值${e}不是一对多映射`);
destEntities.push(rel[0]);
for (const action of cascadeActions as ActionDef<ED, T>[]) {
if (rows instanceof Array) {
if (action === 'create' || typeof action === 'object' && action.action === 'create') {
@ -368,7 +373,24 @@ function checkActionsAndCascadeEntities<
}
}
}
}
if (destEntities.length > 0) {
// 权限判断需要actionAuth的数据这里向cache请求时会根据keepFresh规则进行一定程度的优化。
this.features.cache.refresh('actionAuth', {
data: {
id: 1,
relationId: 1,
paths: 1,
destEntity: 1,
deActions: 1,
},
filter: {
destEntity: {
$in: destEntities as string[],
}
}
});
}
return legalActions;

View File

@ -10,7 +10,9 @@ export type InitializeOptions<ED extends EntityDict & BaseEntityDict> = {
colorDict: ColorDict<ED>;
importations?: Importation<ED, keyof ED, any>[];
exportations?: Exportation<ED, keyof ED, any>[];
selectFreeEntities: (keyof ED)[];
createFreeEntities: (keyof ED)[];
updateFreeEntities: (keyof ED)[];
selectFreeEntities?: (keyof ED)[];
createFreeEntities?: (keyof ED)[];
updateFreeEntities?: (keyof ED)[];
cacheSavedEntities?: (keyof ED)[];
cacheKeepFreshPeriod?: number;
};