重构了cache和token的结构

This commit is contained in:
Xu Chang 2022-04-12 19:40:39 +08:00
parent aa201e55ae
commit d762003ea8
14 changed files with 271 additions and 142 deletions

View File

@ -1,5 +0,0 @@
import { EntityDict } from 'oak-domain/lib/types/Entity';
import { Context as BaseContext } from 'oak-memory-tree-store';
export class FrontContext<ED extends EntityDict> extends BaseContext<ED> {
};

View File

@ -1,11 +1,84 @@
import { EntityDict } from 'oak-domain/lib/types/Entity';
import { EntityDict, OperationResult, SelectionResult } from 'oak-domain/lib/types/Entity';
import { StorageSchema } from "oak-domain/lib/types/Storage";
import { TriggerExecutor, Checker } from 'oak-general-business';
import { BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { TreeStore } from 'oak-memory-tree-store';
import { CacheContext } from './context';
export class CacheStore<ED extends EntityDict> extends TreeStore<ED> {
constructor(storageSchema: StorageSchema<ED>) {
super(storageSchema);
export class CacheStore<ED extends EntityDict & BaseEntityDict> extends TreeStore<ED> {
private executor: TriggerExecutor<ED>;
constructor(storageSchema: StorageSchema<ED>, initialData?: {
[T in keyof ED]?: {
[ID: string]: ED[T]['OpSchema'];
};
}) {
super(storageSchema, true, initialData);
this.executor = new TriggerExecutor();
}
// todo
async operate<T extends keyof ED>(
entity: T,
operation: ED[T]['Operation'],
context: CacheContext<ED>,
params?: Object
): Promise<OperationResult> {
const autoCommit = !context.uuid;
let result;
if (autoCommit) {
await context.begin();
}
try {
await this.executor.preOperation(entity, operation, context);
result = await super.operate(entity, operation, context, params);
await this.executor.postOperation(entity, operation, context);
}
catch (err) {
await context.rollback();
throw err;
}
if (autoCommit) {
await context.commit();
}
return result;
}
async select<T extends keyof ED>(
entity: T,
selection: ED[T]['Selection'],
context: CacheContext<ED>,
params?: Object
): Promise<SelectionResult<ED, T>> {
const autoCommit = !context.uuid;
if (autoCommit) {
await context.begin();
}
let result;
try {
result = await super.select(entity, selection, context, params);
}
catch (err) {
await context.rollback();
throw err;
}
if (autoCommit) {
await context.commit();
}
return result;
}
async count<T extends keyof ED>(
entity: T,
selection: Omit<ED[T]['Selection'], 'data' | 'sorter' | 'action'>,
context: CacheContext<ED>,
params?: Object
): Promise<number> {
throw new Error("Method not implemented.");
}
registerChecker<T extends keyof ED>(checker: Checker<ED, T>) {
this.executor.registerChecker(checker);
}
}

View File

@ -1,8 +1,23 @@
import { EntityDef } from "oak-domain/lib/types/Entity";
import { Context as BaseContext } from 'oak-memory-tree-store';
import { EntityDict } from 'oak-domain/lib/types/Entity';
import { RuntimeContext } from 'oak-general-business';
import { BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { Schema as Application } from 'oak-general-business/src/base-ed/Application/Schema';
import { Schema as Token } from 'oak-general-business/src/base-ed/Token/Schema';
import { Context } from 'oak-memory-tree-store';
import { CacheStore } from './CacheStore';
export class CacheContext<ED extends {
[E: string]: EntityDef;
}> extends BaseContext<ED> {
export class CacheContext<ED extends EntityDict & BaseEntityDict> extends Context<ED> implements RuntimeContext<ED> {
getApplication: () => Pick<Application, 'id'> | undefined;
getToken: () => Pick<Token, 'id' | 'userId' | 'playerId'> | undefined;
constructor(store: CacheStore<ED>, application?: Pick<Application, 'id'>, token?: Pick<Token, 'id' | 'userId' | 'playerId'>) {
super(store);
this.getApplication = () => application;
this.getToken = () => token;
}
on(event: "commit" | "rollback", callback: (context: any) => Promise<void>): void {
throw new Error('disallow cross txn events in FrontContext');
}
};

View File

@ -8,8 +8,8 @@ import { DebugStore } from './debugStore';
import { RuntimeContext } from 'oak-general-business';
export class DebugContext<ED extends BaseEntityDict & EntityDict> extends BaseContext<ED> implements RuntimeContext<ED> {
getApplication: () => Application | undefined;
getToken: () => Token | undefined;
getApplication: () => Pick<Token, 'id' | 'userId' | 'playerId'> | undefined;
getToken: () => Pick<Application, 'id'> | undefined;
async initGetFn(applicationId?: string, tokenValue?: string) {
if (applicationId) {

View File

@ -3,7 +3,7 @@ import { BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { TreeStore } from 'oak-memory-tree-store';
import { DebugContext } from './context';
import { TriggerExecutor, Trigger } from 'oak-general-business';
import { TriggerExecutor, Trigger, Checker } from 'oak-general-business';
import { StorageSchema } from "oak-domain/lib/types/Storage";
export class DebugStore<ED extends EntityDict & BaseEntityDict> extends TreeStore<ED> {
@ -88,5 +88,9 @@ export class DebugStore<ED extends EntityDict & BaseEntityDict> extends TreeStor
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T>) {
this.executor.registerTrigger(trigger);
}
registerChecker<T extends keyof ED>(checker: Checker<ED, T>) {
this.executor.registerChecker(checker);
}
}

View File

@ -4,9 +4,8 @@ import { DebugContext } from './context';
import { FormCreateData, Selection, EntityDict } from "oak-domain/lib/types/Entity";
import { BaseEntityDict as BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { StorageSchema } from 'oak-domain/lib/types/Storage';
import { Trigger, TriggerExecutor } from 'oak-general-business';
import { data as generalData, triggers as generalTriggers } from 'oak-general-business';
import { FrontContext } from '../FrontContext';
import { Checker, Trigger, TriggerExecutor } from 'oak-general-business';
import { data as generalData, triggers as generalTriggers, checkers as generalCheckers } from 'oak-general-business';
async function initDataInStore<ED extends EntityDict & BaseEntityDict>(store: DebugStore<ED>, initialData?: {
[T in keyof ED]?: Array<FormCreateData<ED[T]['OpSchema']>>;
@ -36,19 +35,30 @@ async function initDataInStore<ED extends EntityDict & BaseEntityDict>(store: De
}
export function createDebugStore<ED extends EntityDict & BaseEntityDict>(storageSchema: StorageSchema<ED>, triggers?: Array<Trigger<ED, keyof ED>>, initialData?: {
export function createDebugStore<ED extends EntityDict & BaseEntityDict>(
storageSchema: StorageSchema<ED>,
triggers?: Array<Trigger<ED, keyof ED>>,
checkers?: Array<Checker<ED, keyof ED>>,
initialData?: {
[T in keyof ED]?: Array<FormCreateData<ED[T]['OpSchema']>>;
}){
const executor = new TriggerExecutor<ED>();
const store = new DebugStore<ED>(executor, storageSchema);
generalTriggers.forEach(
ele => store.registerTrigger(ele)
(generalTriggers).forEach(
ele => store.registerTrigger(ele as any)
);
triggers?.forEach(
ele => store.registerTrigger(ele)
);
generalCheckers.forEach(
ele => store.registerChecker(ele as any)
);
checkers?.forEach(
ele => store.registerChecker(ele)
);
// 如果有物化存储的数据使用此数据否则使用initialData初始化debugStore
initDataInStore(store, initialData);
return store;

View File

@ -1,18 +1,118 @@
import { DeduceSelection, EntityDict, OperateParams, OpRecord } from 'oak-domain/lib/types/Entity';
import { Aspect } from 'oak-general-business';
import { Aspect, Checker, checkers as generalCheckers } from 'oak-general-business';
import { Action, Feature } from '../types/Feature';
import { assign } from 'lodash';
import { FrontContext } from '../FrontContext';
import { CacheContext } from '../cacheStore/context';
import { BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { CacheStore } from '../cacheStore/CacheStore';
import { StorageSchema } from 'oak-domain/lib/types/Storage';
import { Schema as Token } from 'oak-general-business/lib/base-ed/Token/Schema';
import { Schema as Application } from 'oak-general-business/lib/base-ed/Application/Schema';
export class Cache<ED extends EntityDict, AD extends Record<string, Aspect<ED>>> extends Feature<ED, AD> {
export class Cache<ED extends EntityDict & BaseEntityDict, AD extends Record<string, Aspect<ED>>> extends Feature<ED, AD> {
cacheStore: CacheStore<ED>;
applicationId: string;
tokenValue?: string;
token?: Pick<Token, 'id' | 'userId' | 'playerId'>;
application?: Pick<Application, 'id'>;
constructor(cacheStore: CacheStore<ED>) {
private async getConstansData() {
await this.getAspectProxy()?.operate({
entity: 'application',
operation: {
data: {
id: 1,
systemId: 1,
system: {
id: 1,
},
},
filter: {
id: this.applicationId,
},
action: 'select',
}
});
if (this.tokenValue) {
await this.getAspectProxy()?.operate({
entity: 'token',
operation: {
action: 'select',
data: {
id: 1,
userId: 1,
user: {
id: 1,
nickname: 1,
},
playerId: 1,
player: {
id: 1,
nickname: 1,
},
},
filter: {
id: this.tokenValue,
},
}
});
}
}
private async setConstants(getData?: true) {
if (getData) {
await this.getConstansData();
}
const context = new CacheContext(this.cacheStore, this.application, this.token);
if (this.tokenValue) {
const { result } = await this.cacheStore.select('token', {
data: {
id: 1,
userId: 1,
playerId: 1,
},
filter: {
id: this.tokenValue,
}
}, context);
this.token = result[0] as any;
}
if (this.applicationId) {
const { result: [application] } = await this.cacheStore.select('application', {
data: {
id: 1,
systemId: 1,
system: {
id: 1,
},
},
filter: {
id: this.applicationId,
}
}, context);
this.application = application as any;
}
await context.commit();
}
constructor(storageSchema: StorageSchema<ED>, applicationId: string, checkers?: Array<Checker<ED, keyof ED>>, tokenValue?: string) {
const cacheStore = new CacheStore(storageSchema);
generalCheckers.forEach(
(checker) => cacheStore.registerChecker(checker as any)
);
if (checkers) {
checkers.forEach(
(checker) => cacheStore.registerChecker(checker)
);
}
super();
this.cacheStore = cacheStore;
this.applicationId = applicationId;
this.tokenValue = tokenValue;
this.setConstants(true);
}
@Action
refresh<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], params?: object) {
return this.getAspectProxy().operate({
@ -24,7 +124,7 @@ export class Cache<ED extends EntityDict, AD extends Record<string, Aspect<ED>>>
@Action
async sync(records: OpRecord<ED>[]) {
const context = new FrontContext(this.cacheStore);
const context = new CacheContext(this.cacheStore);
try {
await this.cacheStore.sync(records, context);
}
@ -37,7 +137,7 @@ export class Cache<ED extends EntityDict, AD extends Record<string, Aspect<ED>>>
@Action
async operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], commit: boolean = true, params?: OperateParams) {
const context = new FrontContext(this.cacheStore);
const context = new CacheContext(this.cacheStore);
let result: Awaited<ReturnType<typeof this.cacheStore.operate>>;
try {
result = await this.cacheStore.operate(entity, operation, context, params);
@ -55,9 +155,17 @@ export class Cache<ED extends EntityDict, AD extends Record<string, Aspect<ED>>>
return result;
}
@Action
async loginByPassword(mobile: string, password: string) {
this.tokenValue = await this.getAspectProxy().loginByPassword({ mobile, password });
await this.setConstants();
return;
}
async get<T extends keyof ED>(options: { entity: T, selection: ED[T]['Selection'], params?: object }) {
const { entity, selection, params } = options;
const context = new FrontContext(this.cacheStore);
const context = new CacheContext(this.cacheStore);
const { result } = await this.cacheStore.select(entity, selection, context, params);
return result;
}
@ -65,4 +173,8 @@ export class Cache<ED extends EntityDict, AD extends Record<string, Aspect<ED>>>
judgeRelation(entity: keyof ED, attr: string) {
return this.cacheStore.judgeRelation(entity, attr);
}
getTokenValue() {
return this.tokenValue;
}
}

View File

@ -1,24 +1,25 @@
import { EntityDict } from 'oak-domain/lib/types/Entity';
import { BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { Aspect } from 'oak-general-business';
import { Aspect, Checker } from 'oak-general-business';
import { Cache } from './cache';
import { Location } from './location';
import { Token } from './token';
import { RunningNode } from './node';
import { CacheStore } from '../cacheStore/CacheStore';
import { StorageSchema } from 'oak-domain/lib/types/Storage';
export function initialize<ED extends EntityDict & BaseEntityDict,
AD extends Record<string, Aspect<ED>>> (cacheStore: CacheStore<ED>): BasicFeatures<ED, AD> {
const cache = new Cache<ED, AD>(cacheStore);
AD extends Record<string, Aspect<ED>>> (
storageSchema: StorageSchema<ED>,
applicationId: string,
checkers?: Array<Checker<ED, keyof ED>>): BasicFeatures<ED, AD> {
const cache = new Cache<ED, AD>(storageSchema, applicationId, checkers);
const location = new Location();
const token = new Token(cache as any);
const runningNode = new RunningNode<ED, AD>(cache);
return {
cache,
location,
token,
runningNode,
};
}
@ -26,6 +27,5 @@ export function initialize<ED extends EntityDict & BaseEntityDict,
export type BasicFeatures<ED extends EntityDict & BaseEntityDict, AD extends Record<string, Aspect<ED>>> = {
cache: Cache<ED, AD>;
location: Location;
token: Token;
runningNode: RunningNode<ED, AD>;
};

View File

@ -1,5 +1,6 @@
import { set, cloneDeep, pull, unset } from 'lodash';
import { DeduceCreateOperation, DeduceFilter, DeduceOperation, DeduceSelection, DeduceUpdateOperation, EntityDict, EntityShape, FormCreateData, OperationResult, OpRecord, SelectionResult } from 'oak-domain/lib/types/Entity';
import { DeduceCreateOperation, DeduceFilter, DeduceOperation, DeduceSelection, DeduceUpdateOperation, EntityDict, EntityShape } from 'oak-domain/lib/types/Entity';
import { BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { Aspect } from 'oak-general-business';
import { combineFilters } from 'oak-domain/lib/store/filter';
import { Action, Feature } from '../types/Feature';
@ -95,7 +96,7 @@ const DEFAULT_PAGINATION: Pagination = {
more: true,
}
class ListNode<ED extends EntityDict, AD extends Record<string, Aspect<ED>>, T extends keyof ED> extends Node<ED, AD, T>{
class ListNode<ED extends EntityDict & BaseEntityDict, AD extends Record<string, Aspect<ED>>, T extends keyof ED> extends Node<ED, AD, T>{
private ids: string[];
protected children: SingleNode<ED, AD, T>[];
protected value: Partial<ED[T]['Schema']>[];
@ -240,7 +241,7 @@ declare type AttrFilter<SH extends EntityShape> = {
[K in keyof SH]: any;
};
class SingleNode<ED extends EntityDict, AD extends Record<string, Aspect<ED>>, T extends keyof ED> extends Node<ED, AD, T>{
class SingleNode<ED extends EntityDict & BaseEntityDict, AD extends Record<string, Aspect<ED>>, T extends keyof ED> extends Node<ED, AD, T>{
private id?: string;
private value?: Partial<ED[T]['Schema']>;
private children: {
@ -266,15 +267,15 @@ class SingleNode<ED extends EntityDict, AD extends Record<string, Aspect<ED>>, T
}
}
composeOperation(): DeduceOperation<ED[T]['Schema']> | undefined {
if (!this.isDirty()) {
composeOperation(action2?: string): DeduceOperation<ED[T]['Schema']> | undefined {
if (!this.isDirty() && !action2) {
return;
}
const action = this.action === 'create' ? {
action: 'create',
data: cloneDeep(this.updateData) || {},
} as DeduceCreateOperation<ED[T]['Schema']> : {
action: this.action || 'update',
action: action2 || this.action || 'update',
data: cloneDeep(this.updateData) || {},
filter: {
id: this.id!,
@ -413,7 +414,7 @@ class SingleNode<ED extends EntityDict, AD extends Record<string, Aspect<ED>>, T
}
export class RunningNode<ED extends EntityDict, AD extends Record<string, Aspect<ED>>> extends Feature<ED, AD> {
export class RunningNode<ED extends EntityDict & BaseEntityDict, AD extends Record<string, Aspect<ED>>> extends Feature<ED, AD> {
private cache: Cache<ED, AD>;
private schema?: StorageSchema<ED>;
private root: Record<string, SingleNode<ED, AD, keyof ED> | ListNode<ED, AD, keyof ED>>;
@ -732,9 +733,9 @@ export class RunningNode<ED extends EntityDict, AD extends Record<string, Aspect
}
@Action
async execute(path: string, isTry?: boolean) {
async execute(path: string, action?: string, isTry?: boolean) {
const node = await this.findNode(path);
const operation = node.composeOperation();
const operation = node.composeOperation(action);
// 先在cache中尝试能否执行如果权限上否决了在这里就失败
if (operation instanceof Array) {
for (const oper of operation) {

View File

@ -1,81 +0,0 @@
import { BaseEntityDict as BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { aspectDict as basicAspectDict } from 'oak-general-business';
import { Action, Feature } from '../types/Feature';
import { Cache } from './cache';
export class Token extends Feature<BaseEntityDict, typeof basicAspectDict> {
tokenValue?: string;
cache: Cache<BaseEntityDict, typeof basicAspectDict>;
constructor(cache: Cache<BaseEntityDict, typeof basicAspectDict>) {
super();
this.cache = cache;
}
getValue() {
return this.tokenValue;
}
async getToken() {
const [token] = await this.cache.get({
entity: 'token',
selection: {
data: {
id: 1,
userId: 1,
user: {
id: 1,
name: 1,
nickname: 1,
},
playerId: 1,
player: {
id: 1,
name: 1,
nickname: 1,
},
},
filter: {
id: this.tokenValue,
},
}});
return token;
}
/* async get(type: 'value' | 'token') {
if (type === 'value') {
return this.tokenValue;
}
if (!this.tokenValue) {
return undefined;
}
const [token] = await this.cache.get({
entity: 'token',
selection: {
data: {
id: 1,
userId: 1,
user: {
id: 1,
name: 1,
nickname: 1,
},
playerId: 1,
player: {
id: 1,
name: 1,
nickname: 1,
},
},
filter: {
id: this.tokenValue,
},
}});
return token;
} */
@Action
async loginByPassword(mobile: string, password: string) {
this.tokenValue = await this.getAspectProxy().loginByPassword({ mobile, password });
return;
}
}

View File

@ -1,5 +1,5 @@
import { StorageSchema } from 'oak-domain/lib/types/Storage';
import { Trigger } from "oak-general-business";
import { Checker, Trigger } from "oak-general-business";
import { BaseEntityDict as BaseEntityDict } from 'oak-general-business/lib/base-ed/EntityDict';
import { aspectDict as basicAspectDict } from 'oak-general-business';
@ -19,6 +19,7 @@ function createAspectProxy<ED extends BaseEntityDict & EntityDict,
FD extends Record<string, Feature<ED, AD>>>(
storageSchema: StorageSchema<ED>,
triggers: Array<Trigger<ED, keyof ED>>,
checkers: Array<Checker<ED, keyof ED>>,
applicationId: string,
features: BasicFeatures<ED, AD> & FD,
aspectDict?: AD,
@ -31,11 +32,11 @@ function createAspectProxy<ED extends BaseEntityDict & EntityDict,
}
else {
// todo initialData
const debugStore = createDebugStore(storageSchema, triggers, initialData);
const debugStore = createDebugStore(storageSchema, triggers, checkers, initialData);
const connectAspectToDebugStore = (aspect: Aspect<ED>): (p: Parameters<typeof aspect>[0]) => ReturnType<typeof aspect> => {
return async (params: Parameters<typeof aspect>[0]) => {
const tokenValue = await features.token.getValue();
const tokenValue = features.cache.getTokenValue();
const runningContext = new DebugContext(debugStore, applicationId, tokenValue);
await runningContext.begin();
@ -70,12 +71,12 @@ export function initialize<ED extends EntityDict & BaseEntityDict, AD extends Re
applicationId: string,
createFeatures: (basicFeatures: BasicFeatures<ED, AD>) => FD,
triggers?: Array<Trigger<ED, keyof ED>>,
checkers?: Array<Checker<ED, keyof ED>>,
aspectDict?: AD,
initialData?: {
[T in keyof ED]?: Array<ED[T]['OpSchema']>;
}) {
const cacheStore = new CacheStore<ED>(storageSchema);
const basicFeatures = createBasicFeatures<ED, AD>(cacheStore);
const basicFeatures = createBasicFeatures<ED, AD>(storageSchema, applicationId, checkers);
basicFeatures.runningNode.setStorageSchema(storageSchema);
const userDefinedfeatures = createFeatures(basicFeatures);
@ -88,7 +89,7 @@ export function initialize<ED extends EntityDict & BaseEntityDict, AD extends Re
// todo default triggers
const aspectProxy = createAspectProxy<ED, AD, FD>(storageSchema, triggers || [],
const aspectProxy = createAspectProxy<ED, AD, FD>(storageSchema, triggers || [], checkers || [],
applicationId, features, aspectDict, initialData);
keys(features).forEach(
@ -107,7 +108,6 @@ export function initialize<ED extends EntityDict & BaseEntityDict, AD extends Re
export * from './features/node';
export * from './FrontContext';
export * from './types/Feature';
export * from './types/Pagination';
export * from './features/cache';

View File

@ -1,6 +1,5 @@
import { Aspect } from "oak-general-business";
import { EntityDict } from "oak-domain/lib/types/Entity";
import { FrontContext } from "../FrontContext";
export type AspectProxy<ED extends EntityDict, AD extends Record<string, Aspect<ED>>> = {
[K in keyof AD]: (p: Parameters<AD[K]>[0]) => ReturnType<AD[K]>;

View File

@ -2,7 +2,6 @@ import { pull } from 'lodash';
import { Aspect } from 'oak-general-business';
import { EntityDict } from 'oak-domain/lib/types/Entity';
import { aspectDict as basicAspectDict} from 'oak-general-business';
import { FrontContext } from '../FrontContext';
import { AspectProxy } from './AspectProxy';

View File

@ -1,7 +1,9 @@
import { unset } from 'lodash';
type A = {
name: 'bbb'
[k: string]: string;
};
const test = [, 'xc', 1234];
unset(test, 1);
console.log(test[1]);
type B = keyof A;
console.log('xc'.lastIndexOf('.'));
const b: B = 1;