缓存在某一时间戳状态下,对某行进行某个action的判断结果

This commit is contained in:
Xu Chang 2024-09-15 15:26:05 +08:00
parent f6d6204ce9
commit 16ce411444
14 changed files with 211 additions and 57 deletions

View File

@ -5,7 +5,6 @@ import { makeIntrinsicLogics } from 'oak-domain/lib/store/IntrinsicLogics';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { LocalStorage } from '../features/localStorage';
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
async function initDataInStore(store, initialData, stat) {
store.resetInitialData(initialData, stat);
}

View File

@ -34,6 +34,7 @@ export declare class Cache<ED extends EntityDict & BaseEntityDict> extends Featu
private attrUpdateMatrix?;
private connector;
private baseRelationAuth;
private entityActionAuthDict;
constructor(storageSchema: StorageSchema<ED>, connector: Connector<ED, SyncContext<ED>>, frontendContextBuilder: (store: CacheStore<ED>) => SyncContext<ED>, checkers: Array<Checker<ED, keyof ED, SyncContext<ED>>>, localStorage: LocalStorage, common: CommonConfiguration<ED>);
/**
* cache中需要缓存的数据
@ -96,7 +97,7 @@ export declare class Cache<ED extends EntityDict & BaseEntityDict> extends Featu
action: ED[T]['Action'];
data?: ED[T]['Operation']['data'];
filter?: ED[T]['Operation']['filter'];
}, checkerTypes?: CheckerType[]): boolean | OakUserException<ED>;
}, checkerTypes?: CheckerType[]): boolean | { [A in ED[T]["Action"]]: boolean | OakUserException<ED>; }[ED[T]["Action"]];
redoOperation(opers: Array<{
entity: keyof ED;
operation: ED[keyof ED]['Operation'];

View File

@ -1,5 +1,5 @@
import { Feature } from '../types/Feature';
import { pull, intersection, unset, union } from 'oak-domain/lib/utils/lodash';
import { set, pull, intersection, unset, union } from 'oak-domain/lib/utils/lodash';
import { CacheStore } from '../cacheStore/CacheStore';
import { OakRowUnexistedException, OakException, OakUserException } from 'oak-domain/lib/types/Exception';
import { assert } from 'oak-domain/lib/utils/assert';
@ -22,6 +22,8 @@ export class Cache extends Feature {
attrUpdateMatrix;
connector;
baseRelationAuth;
// 缓存在某一时间戳状态下对某行进行某个action的判断结果
entityActionAuthDict;
constructor(storageSchema, connector, frontendContextBuilder, checkers, localStorage, common) {
super();
this.syncEventsCallbacks = [];
@ -37,6 +39,7 @@ export class Cache extends Feature {
// 现在这个init变成了异步行为不知道有没有影响。by Xc 20231126
this.initPromise = new Promise((resolve) => this.initSavedLogic(resolve));
this.buildEntityGraph();
this.entityActionAuthDict = {};
}
/**
* 处理cache中需要缓存的数据
@ -86,7 +89,7 @@ export class Cache extends Feature {
this.refreshing++;
const { result, opRecords, message } = await this.connector.callAspect(name, params, ignoreContext ? undefined : this.context || this.contextBuilder());
callback && callback(result, opRecords);
if (opRecords) {
if (opRecords?.length) {
this.syncInner(opRecords);
}
this.refreshing--;
@ -257,14 +260,19 @@ export class Cache extends Feature {
syncInner(records) {
this.begin();
this.cacheStore.sync(records, this.context);
const ts2 = this.cacheStore.getLastUpdateTs();
// 同步以后当前所有缓存的action结果都不成立了
this.entityActionAuthDict = {};
// 唤起同步注册的回调
this.syncEventsCallbacks.map((ele) => ele(records));
this.context.commit();
this.context = undefined;
}
sync(records) {
this.syncInner(records);
this.publish();
if (records.length) {
this.syncInner(records);
this.publish();
}
}
/**
* 前端缓存做operation只可能是测试权限必然回滚
@ -325,10 +333,24 @@ export class Cache extends Feature {
return result;
}
checkOperation(entity, operation, checkerTypes) {
// 缓存同一id对同一action的判断避免性能低下
const { action, data, filter } = operation;
let id;
let ts;
if (filter && !data && typeof filter.id === 'string') {
id = filter.id;
ts = this.cacheStore.getLastUpdateTs();
if (this.entityActionAuthDict[ts]?.[entity]?.[id]?.hasOwnProperty(action)) {
return this.entityActionAuthDict[ts][entity][id][action];
}
}
const rollback = this.begin(true);
try {
this.cacheStore.check(entity, operation, this.context, checkerTypes);
rollback && rollback();
if (id && ts) {
set(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, true);
}
return true;
}
catch (err) {
@ -336,6 +358,9 @@ export class Cache extends Feature {
if (err instanceof OakRowUnexistedException) {
// 有外键缺失,尝试发一下请求
this.fetchRows(err.getRows());
if (id && ts) {
set(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, false);
}
return false;
}
/**
@ -345,6 +370,9 @@ export class Cache extends Feature {
/* if (!(err instanceof OakUserException)) {
throw err;
} */
if (id && ts) {
set(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, err);
}
return err;
}
}

View File

@ -3,6 +3,7 @@ import { cloneDeep, unset, uniq } from "oak-domain/lib/utils/lodash";
import { combineFilters, getRelevantIds } from "oak-domain/lib/store/filter";
import { createOperationsFromModies } from 'oak-domain/lib/store/modi';
import { judgeRelation } from "oak-domain/lib/store/relation";
import { CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types";
import { Feature } from '../types/Feature';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
export const MODI_NEXT_PATH_SUFFIX = ':next';
@ -729,9 +730,14 @@ class ListNode extends EntityNode {
}
}
const id = item.id || generateNewId();
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign(item, { id }),
data: Object.assign(item, {
id,
[CreateAtAttribute]: now,
[UpdateAtAttribute]: now,
}),
});
return id;
}
@ -748,7 +754,9 @@ class ListNode extends EntityNode {
removeItemInner(id, lsn) {
this.ulManager.push(lsn, {
action: 'remove',
data: {},
data: {
[DeleteAtAttribute]: Date.now(),
},
filter: {
id,
},
@ -794,7 +802,9 @@ class ListNode extends EntityNode {
updateItemInner(lsn, data, id, action) {
this.ulManager.push(lsn, {
action: action || 'update',
data,
data: Object.assign({}, data, {
[UpdateAtAttribute]: Date.now(),
}),
filter: {
id,
},
@ -1215,9 +1225,14 @@ class SingleNode extends EntityNode {
});
}
}
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign({}, data, { id }),
data: Object.assign({}, data, {
id,
[CreateAtAttribute]: now,
[UpdateAtAttribute]: now,
}),
});
this.refreshListChildren();
this.setDirty();
@ -1238,7 +1253,9 @@ class SingleNode extends EntityNode {
undefinedAttrs.forEach((attr) => unset(data, attr));
this.ulManager.push(lsn, {
action: action || 'update',
data,
data: Object.assign({}, data, {
[UpdateAtAttribute]: Date.now(),
}),
filter: {
id: this.getId(),
}
@ -1269,7 +1286,9 @@ class SingleNode extends EntityNode {
assert(id);
this.ulManager.push(lsn, {
action: 'remove',
data: {},
data: {
[DeleteAtAttribute]: Date.now(),
},
filter: {
id,
}

View File

@ -105,7 +105,7 @@ export class SubScriber extends Feature {
});
if (!optionInited) {
let count = 0;
socket.on('connect_error', async () => {
socket.on('connect_error', async (err) => {
count++;
if (count > 50) {
// 可能socket地址改变了刷新重连
@ -113,7 +113,7 @@ export class SubScriber extends Feature {
}
socket.removeAllListeners();
socket.disconnect();
this.socket = null;
this.url = undefined;
await this.connect();
resolve(undefined);
});

18
es/page.react.d.ts vendored
View File

@ -49,21 +49,21 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
redirectTo<T2_1 extends keyof ED>(options: {
url: string;
} & OakNavigateToParameters<ED, T2_1>, state?: Record<string, any> | undefined, disableNamespace?: boolean | undefined): Promise<void>;
addItem<T_1 extends keyof ED>(data: Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
addItem<T extends keyof ED>(data: Omit<ED[T]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
}, path?: string | undefined): string;
addItems<T_2 extends keyof ED>(data: (Omit<ED[T_2]["CreateSingle"]["data"], "id"> & {
addItems<T_1 extends keyof ED>(data: (Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
})[], path?: string | undefined): string[];
removeItem(id: string, path?: string | undefined): void;
removeItems(ids: string[], path?: string | undefined): void;
updateItem<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
updateItem<T_2 extends keyof ED>(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
recoverItem(id: string, path?: string | undefined): void;
recoverItems(ids: string[], path?: string | undefined): void;
resetItem(id: string, path?: string | undefined): void;
update<T_5 extends keyof ED>(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void;
create<T_6 extends keyof ED>(data: Omit<ED[T_6]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
update<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
create<T_5 extends keyof ED>(data: Omit<ED[T_5]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
remove(path?: string | undefined): void;
isCreation(path?: string | undefined): boolean;
clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void;
@ -75,9 +75,9 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
}[] | undefined): Promise<void>;
isDirty(path?: string | undefined): boolean;
getFreshValue(path?: string | undefined): Partial<import("oak-domain/lib/types").GeneralEntityShape> | Partial<import("oak-domain/lib/types").GeneralEntityShape>[] | undefined;
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
getOperations<T_7 extends keyof ED>(path?: string | undefined): {
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | { [A in ED[T2_2]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[T2_2]["Action"]];
tryExecute(path?: string | undefined, action?: string | undefined): boolean | { [A_1 in ED[keyof ED]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[keyof ED]["Action"]];
getOperations<T_6 extends keyof ED>(path?: string | undefined): {
entity: keyof ED;
operation: ED[keyof ED]["Operation"];
}[] | undefined;

View File

@ -7,6 +7,7 @@ const DebugStore_1 = require("./DebugStore");
const IntrinsicLogics_1 = require("oak-domain/lib/store/IntrinsicLogics");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const localStorage_1 = require("../features/localStorage");
const lodash_1 = require("oak-domain/lib/utils/lodash");
async function initDataInStore(store, initialData, stat) {
store.resetInitialData(initialData, stat);
}
@ -41,8 +42,8 @@ async function materializeData(data, stat, localStorage) {
async function execWatcher(store, watcher, context) {
if (watcher.hasOwnProperty('actionData')) {
const { entity, action, filter, actionData } = watcher;
const filter2 = typeof filter === 'function' ? await filter() : filter;
const data = typeof actionData === 'function' ? await actionData() : actionData; // 这里有个奇怪的编译错误,不理解 by Xc
const filter2 = typeof filter === 'function' ? await filter() : (0, lodash_1.cloneDeep)(filter);
const data = typeof actionData === 'function' ? await actionData() : (0, lodash_1.cloneDeep)(actionData); // 这里有个奇怪的编译错误,不理解 by Xc
const result = await store.operate(entity, {
id: await (0, uuid_1.generateNewIdAsync)(),
action: action,

View File

@ -34,6 +34,7 @@ export declare class Cache<ED extends EntityDict & BaseEntityDict> extends Featu
private attrUpdateMatrix?;
private connector;
private baseRelationAuth;
private entityActionAuthDict;
constructor(storageSchema: StorageSchema<ED>, connector: Connector<ED, SyncContext<ED>>, frontendContextBuilder: (store: CacheStore<ED>) => SyncContext<ED>, checkers: Array<Checker<ED, keyof ED, SyncContext<ED>>>, localStorage: LocalStorage, common: CommonConfiguration<ED>);
/**
* cache中需要缓存的数据
@ -96,7 +97,7 @@ export declare class Cache<ED extends EntityDict & BaseEntityDict> extends Featu
action: ED[T]['Action'];
data?: ED[T]['Operation']['data'];
filter?: ED[T]['Operation']['filter'];
}, checkerTypes?: CheckerType[]): boolean | OakUserException<ED>;
}, checkerTypes?: CheckerType[]): boolean | { [A in ED[T]["Action"]]: boolean | OakUserException<ED>; }[ED[T]["Action"]];
redoOperation(opers: Array<{
entity: keyof ED;
operation: ED[keyof ED]['Operation'];

View File

@ -25,6 +25,8 @@ class Cache extends Feature_1.Feature {
attrUpdateMatrix;
connector;
baseRelationAuth;
// 缓存在某一时间戳状态下对某行进行某个action的判断结果
entityActionAuthDict;
constructor(storageSchema, connector, frontendContextBuilder, checkers, localStorage, common) {
super();
this.syncEventsCallbacks = [];
@ -40,6 +42,7 @@ class Cache extends Feature_1.Feature {
// 现在这个init变成了异步行为不知道有没有影响。by Xc 20231126
this.initPromise = new Promise((resolve) => this.initSavedLogic(resolve));
this.buildEntityGraph();
this.entityActionAuthDict = {};
}
/**
* 处理cache中需要缓存的数据
@ -89,7 +92,7 @@ class Cache extends Feature_1.Feature {
this.refreshing++;
const { result, opRecords, message } = await this.connector.callAspect(name, params, ignoreContext ? undefined : this.context || this.contextBuilder());
callback && callback(result, opRecords);
if (opRecords) {
if (opRecords?.length) {
this.syncInner(opRecords);
}
this.refreshing--;
@ -260,14 +263,19 @@ class Cache extends Feature_1.Feature {
syncInner(records) {
this.begin();
this.cacheStore.sync(records, this.context);
const ts2 = this.cacheStore.getLastUpdateTs();
// 同步以后当前所有缓存的action结果都不成立了
this.entityActionAuthDict = {};
// 唤起同步注册的回调
this.syncEventsCallbacks.map((ele) => ele(records));
this.context.commit();
this.context = undefined;
}
sync(records) {
this.syncInner(records);
this.publish();
if (records.length) {
this.syncInner(records);
this.publish();
}
}
/**
* 前端缓存做operation只可能是测试权限必然回滚
@ -328,10 +336,24 @@ class Cache extends Feature_1.Feature {
return result;
}
checkOperation(entity, operation, checkerTypes) {
// 缓存同一id对同一action的判断避免性能低下
const { action, data, filter } = operation;
let id;
let ts;
if (filter && !data && typeof filter.id === 'string') {
id = filter.id;
ts = this.cacheStore.getLastUpdateTs();
if (this.entityActionAuthDict[ts]?.[entity]?.[id]?.hasOwnProperty(action)) {
return this.entityActionAuthDict[ts][entity][id][action];
}
}
const rollback = this.begin(true);
try {
this.cacheStore.check(entity, operation, this.context, checkerTypes);
rollback && rollback();
if (id && ts) {
(0, lodash_1.set)(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, true);
}
return true;
}
catch (err) {
@ -339,6 +361,9 @@ class Cache extends Feature_1.Feature {
if (err instanceof Exception_1.OakRowUnexistedException) {
// 有外键缺失,尝试发一下请求
this.fetchRows(err.getRows());
if (id && ts) {
(0, lodash_1.set)(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, false);
}
return false;
}
/**
@ -348,6 +373,9 @@ class Cache extends Feature_1.Feature {
/* if (!(err instanceof OakUserException)) {
throw err;
} */
if (id && ts) {
(0, lodash_1.set)(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, err);
}
return err;
}
}

View File

@ -6,6 +6,7 @@ const lodash_1 = require("oak-domain/lib/utils/lodash");
const filter_1 = require("oak-domain/lib/store/filter");
const modi_1 = require("oak-domain/lib/store/modi");
const relation_1 = require("oak-domain/lib/store/relation");
const types_1 = require("oak-domain/lib/types");
const Feature_1 = require("../types/Feature");
const uuid_1 = require("oak-domain/lib/utils/uuid");
exports.MODI_NEXT_PATH_SUFFIX = ':next';
@ -732,9 +733,14 @@ class ListNode extends EntityNode {
}
}
const id = item.id || (0, uuid_1.generateNewId)();
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign(item, { id }),
data: Object.assign(item, {
id,
[types_1.CreateAtAttribute]: now,
[types_1.UpdateAtAttribute]: now,
}),
});
return id;
}
@ -751,7 +757,9 @@ class ListNode extends EntityNode {
removeItemInner(id, lsn) {
this.ulManager.push(lsn, {
action: 'remove',
data: {},
data: {
[types_1.DeleteAtAttribute]: Date.now(),
},
filter: {
id,
},
@ -797,7 +805,9 @@ class ListNode extends EntityNode {
updateItemInner(lsn, data, id, action) {
this.ulManager.push(lsn, {
action: action || 'update',
data,
data: Object.assign({}, data, {
[types_1.UpdateAtAttribute]: Date.now(),
}),
filter: {
id,
},
@ -1218,9 +1228,14 @@ class SingleNode extends EntityNode {
});
}
}
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign({}, data, { id }),
data: Object.assign({}, data, {
id,
[types_1.CreateAtAttribute]: now,
[types_1.UpdateAtAttribute]: now,
}),
});
this.refreshListChildren();
this.setDirty();
@ -1241,7 +1256,9 @@ class SingleNode extends EntityNode {
undefinedAttrs.forEach((attr) => (0, lodash_1.unset)(data, attr));
this.ulManager.push(lsn, {
action: action || 'update',
data,
data: Object.assign({}, data, {
[types_1.UpdateAtAttribute]: Date.now(),
}),
filter: {
id: this.getId(),
}
@ -1272,7 +1289,9 @@ class SingleNode extends EntityNode {
(0, assert_1.assert)(id);
this.ulManager.push(lsn, {
action: 'remove',
data: {},
data: {
[types_1.DeleteAtAttribute]: Date.now(),
},
filter: {
id,
}

View File

@ -109,7 +109,7 @@ class SubScriber extends Feature_1.Feature {
});
if (!optionInited) {
let count = 0;
socket.on('connect_error', async () => {
socket.on('connect_error', async (err) => {
count++;
if (count > 50) {
// 可能socket地址改变了刷新重连
@ -117,7 +117,7 @@ class SubScriber extends Feature_1.Feature {
}
socket.removeAllListeners();
socket.disconnect();
this.socket = null;
this.url = undefined;
await this.connect();
resolve(undefined);
});

18
lib/page.react.d.ts vendored
View File

@ -49,21 +49,21 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
redirectTo<T2_1 extends keyof ED>(options: {
url: string;
} & OakNavigateToParameters<ED, T2_1>, state?: Record<string, any> | undefined, disableNamespace?: boolean | undefined): Promise<void>;
addItem<T_1 extends keyof ED>(data: Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
addItem<T extends keyof ED>(data: Omit<ED[T]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
}, path?: string | undefined): string;
addItems<T_2 extends keyof ED>(data: (Omit<ED[T_2]["CreateSingle"]["data"], "id"> & {
addItems<T_1 extends keyof ED>(data: (Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
id?: string | undefined;
})[], path?: string | undefined): string[];
removeItem(id: string, path?: string | undefined): void;
removeItems(ids: string[], path?: string | undefined): void;
updateItem<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
updateItem<T_2 extends keyof ED>(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void;
updateItems<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
recoverItem(id: string, path?: string | undefined): void;
recoverItems(ids: string[], path?: string | undefined): void;
resetItem(id: string, path?: string | undefined): void;
update<T_5 extends keyof ED>(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void;
create<T_6 extends keyof ED>(data: Omit<ED[T_6]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
update<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
create<T_5 extends keyof ED>(data: Omit<ED[T_5]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
remove(path?: string | undefined): void;
isCreation(path?: string | undefined): boolean;
clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void;
@ -75,9 +75,9 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
}[] | undefined): Promise<void>;
isDirty(path?: string | undefined): boolean;
getFreshValue(path?: string | undefined): Partial<import("oak-domain/lib/types").GeneralEntityShape> | Partial<import("oak-domain/lib/types").GeneralEntityShape>[] | undefined;
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException<ED>;
getOperations<T_7 extends keyof ED>(path?: string | undefined): {
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | { [A in ED[T2_2]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[T2_2]["Action"]];
tryExecute(path?: string | undefined, action?: string | undefined): boolean | { [A_1 in ED[keyof ED]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[keyof ED]["Action"]];
getOperations<T_6 extends keyof ED>(path?: string | undefined): {
entity: keyof ED;
operation: ED[keyof ED]["Operation"];
}[] | undefined;

View File

@ -2,7 +2,7 @@ import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, Check
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { AspectDict as CommonAspectDict } from 'oak-common-aspect';
import { Feature } from '../types/Feature';
import { merge, pull, intersection, omit, unset, union } from 'oak-domain/lib/utils/lodash';
import { set, pull, intersection, omit, unset, union } 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';
@ -49,6 +49,17 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
private attrUpdateMatrix?: AttrUpdateMatrix<ED>;
private connector: Connector<ED, SyncContext<ED>>;
private baseRelationAuth: BaseRelationAuth<ED>;
// 缓存在某一时间戳状态下对某行进行某个action的判断结果
private entityActionAuthDict: {
[ts: number]: {
[T in keyof ED]?: {
[I: string]: {
[A in ED[T]['Action']]: boolean | OakUserException<ED>;
}
};
};
};
constructor(
storageSchema: StorageSchema<ED>,
@ -81,6 +92,7 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
);
this.buildEntityGraph();
this.entityActionAuthDict = {};
}
/**
@ -151,7 +163,7 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
this.refreshing++;
const { result, opRecords, message } = await this.connector.callAspect(name as string, params, ignoreContext ? undefined : this.context || this.contextBuilder());
callback && callback(result, opRecords);
if (opRecords) {
if (opRecords?.length) {
this.syncInner(opRecords);
}
this.refreshing--;
@ -370,6 +382,10 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
this.begin();
this.cacheStore!.sync(records, this.context!);
const ts2 = this.cacheStore.getLastUpdateTs();
// 同步以后当前所有缓存的action结果都不成立了
this.entityActionAuthDict = {};
// 唤起同步注册的回调
this.syncEventsCallbacks.map((ele) => ele(records));
this.context!.commit();
@ -377,8 +393,10 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
}
sync(records: OpRecord<ED>[]) {
this.syncInner(records);
this.publish();
if (records.length) {
this.syncInner(records);
this.publish();
}
}
/**
@ -455,10 +473,25 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
},
checkerTypes?: CheckerType[]
) {
// 缓存同一id对同一action的判断避免性能低下
const { action, data, filter } = operation;
let id: string | undefined;
let ts: number | undefined;
if (filter && !data && typeof filter.id === 'string') {
id = filter.id;
ts = this.cacheStore.getLastUpdateTs();
if (this.entityActionAuthDict[ts]?.[entity]?.[id]?.hasOwnProperty(action)) {
return this.entityActionAuthDict[ts]![entity]![id]![action]!;
}
}
const rollback = this.begin(true);
try {
this.cacheStore!.check(entity, operation, this.context!, checkerTypes);
rollback && rollback();
if (id && ts) {
set(this.entityActionAuthDict, `${ts}.${entity as string}.${id}.${action}`, true);
}
return true;
}
catch (err) {
@ -466,6 +499,9 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
if (err instanceof OakRowUnexistedException) {
// 有外键缺失,尝试发一下请求
this.fetchRows(err.getRows());
if (id && ts) {
set(this.entityActionAuthDict, `${ts}.${entity as string}.${id}.${action}`, false);
}
return false;
}
/**
@ -475,6 +511,9 @@ export class Cache<ED extends EntityDict & BaseEntityDict> extends Feature {
/* if (!(err instanceof OakUserException)) {
throw err;
} */
if (id && ts) {
set(this.entityActionAuthDict, `${ts}.${entity as string}.${id}.${action}`, err);
}
return err as OakUserException<ED>;
}
}

View File

@ -3,7 +3,7 @@ import { cloneDeep, difference, unset, merge, uniq, omit } from "oak-domain/lib/
import { combineFilters, getRelevantIds } from "oak-domain/lib/store/filter";
import { createOperationsFromModies } from 'oak-domain/lib/store/modi';
import { judgeRelation } from "oak-domain/lib/store/relation";
import { EntityDict, StorageSchema, OpRecord, CreateOpResult, RemoveOpResult, AspectWrapper, AuthDefDict, CascadeRelationItem, CascadeActionItem, UpdateOpResult } from "oak-domain/lib/types";
import { EntityDict, StorageSchema, OpRecord, CreateOpResult, RemoveOpResult, AspectWrapper, AuthDefDict, CascadeRelationItem, CascadeActionItem, UpdateOpResult, CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types";
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { AspectDict as CommonAspectDict } from 'oak-common-aspect';
@ -900,9 +900,14 @@ class ListNode<
}
}
const id = item.id || generateNewId();
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign(item, { id }),
data: Object.assign(item, {
id,
[CreateAtAttribute]: now,
[UpdateAtAttribute]: now,
}) as ED[T]['CreateSingle']['data'],
} as Omit<ED[T]['CreateSingle'], "id">);
return id;
}
@ -924,7 +929,9 @@ class ListNode<
private removeItemInner(id: string, lsn: number) {
this.ulManager.push(lsn, {
action: 'remove',
data: {},
data: {
[DeleteAtAttribute]: Date.now(),
},
filter: {
id,
},
@ -990,7 +997,9 @@ class ListNode<
) {
this.ulManager.push(lsn, {
action: action || 'update',
data,
data: Object.assign({}, data, {
[UpdateAtAttribute]: Date.now(),
}),
filter: {
id,
},
@ -1504,9 +1513,15 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
});
}
}
const now = Date.now();
this.ulManager.push(lsn, {
action: 'create',
data: Object.assign({}, data, { id }),
data: Object.assign({}, data, {
id,
[CreateAtAttribute]: now,
[UpdateAtAttribute]: now,
} as ED[T]['CreateSingle']['data']),
} as Omit<ED[T]['CreateSingle'], "id">);
this.refreshListChildren();
this.setDirty();
@ -1531,7 +1546,9 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
this.ulManager.push(lsn, {
action: action || 'update',
data,
data: Object.assign({}, data, {
[UpdateAtAttribute]: Date.now(),
}),
filter: {
id: this.getId()!,
}
@ -1565,7 +1582,9 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
assert(id);
this.ulManager.push(lsn, {
action: 'remove',
data: {},
data: {
[DeleteAtAttribute]: Date.now(),
},
filter: {
id,
}