增加了cache的缓存和keepFresh行为
This commit is contained in:
parent
b232d0c4c4
commit
8eba310582
|
|
@ -2,4 +2,6 @@ export declare const LOCAL_STORAGE_KEYS: {
|
|||
debugStore: string;
|
||||
debugStoreStat: string;
|
||||
localeLng: string;
|
||||
cacheSaved: string;
|
||||
cacheRefreshRecord: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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); });
|
||||
|
|
|
|||
|
|
@ -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必须满足:
|
||||
* 1)没有indexFrom和count
|
||||
* 2)没要求getCount
|
||||
* 3)查询的projection和filter只限定在该对象自身属性上
|
||||
* 4)有filter
|
||||
* @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 {};
|
||||
|
|
|
|||
|
|
@ -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*/];
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>>;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
// 这里向服务器请求相应的actionAuth,cache层会对请求加以优化,避免反复过频的不必要取数据
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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']> {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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必须满足:
|
||||
* 1)没有indexFrom和count
|
||||
* 2)没要求getCount
|
||||
* 3)查询的projection和filter只限定在该对象自身属性上
|
||||
* 4)有filter
|
||||
* @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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 || []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>[];
|
||||
|
||||
// 这里向服务器请求相应的actionAuth,cache层会对请求加以优化,避免反复过频的不必要取数据
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue