大改了框架结构
This commit is contained in:
parent
fde3c0b1da
commit
499cb6d592
|
|
@ -9,4 +9,4 @@ export declare function select<ED extends EntityDict, T extends keyof ED>(option
|
||||||
entity: T;
|
entity: T;
|
||||||
selection: ED[T]['Selection'];
|
selection: ED[T]['Selection'];
|
||||||
params?: object;
|
params?: object;
|
||||||
}, context: RuntimeContext<ED>): Promise<import("oak-domain/lib/types/Entity").SelectionResult<ED, T>>;
|
}, context: RuntimeContext<ED>): Promise<import("oak-domain/lib/types/Entity").SelectionResult2<ED[T]["Schema"], ED[T]["Selection"]["data"]>>;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { EntityDef as System } from "./System/Schema";
|
||||||
import { EntityDef as Token } from "./Token/Schema";
|
import { EntityDef as Token } from "./Token/Schema";
|
||||||
import { EntityDef as User } from "./User/Schema";
|
import { EntityDef as User } from "./User/Schema";
|
||||||
import { EntityDef as WechatUser } from "./WechatUser/Schema";
|
import { EntityDef as WechatUser } from "./WechatUser/Schema";
|
||||||
export declare type BaseEntityDict = {
|
export declare type EntityDict = {
|
||||||
address: Address;
|
address: Address;
|
||||||
application: Application;
|
application: Application;
|
||||||
area: Area;
|
area: Area;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { EntityDict, OperationResult } from 'oak-domain/lib/types/Entity';
|
||||||
|
import { StorageSchema } from "oak-domain/lib/types/Storage";
|
||||||
|
import { Checker } from 'oak-domain/lib/types/Auth';
|
||||||
|
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
||||||
|
import { TreeStore } from 'oak-memory-tree-store';
|
||||||
|
import { CacheContext } from './context';
|
||||||
|
export declare class CacheStore<ED extends EntityDict & BaseEntityDict> extends TreeStore<ED> {
|
||||||
|
private executor;
|
||||||
|
constructor(storageSchema: StorageSchema<ED>, initialData?: {
|
||||||
|
[T in keyof ED]?: {
|
||||||
|
[ID: string]: ED[T]['OpSchema'];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: CacheContext<ED>, params?: Object): Promise<OperationResult>;
|
||||||
|
select<T extends keyof ED, S extends ED[T]['Selection']>(entity: T, selection: S, context: CacheContext<ED>, params?: Object): Promise<import("oak-domain/lib/types/Entity").SelectionResult2<ED[T]["Schema"], S["data"]>>;
|
||||||
|
count<T extends keyof ED>(entity: T, selection: Omit<ED[T]['Selection'], 'data' | 'sorter' | 'action'>, context: CacheContext<ED>, params?: Object): Promise<number>;
|
||||||
|
registerChecker<T extends keyof ED>(checker: Checker<ED, T>): void;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.CacheStore = void 0;
|
||||||
|
const TriggerExecutor_1 = require("oak-domain/lib/store/TriggerExecutor");
|
||||||
|
const oak_memory_tree_store_1 = require("oak-memory-tree-store");
|
||||||
|
class CacheStore extends oak_memory_tree_store_1.TreeStore {
|
||||||
|
executor;
|
||||||
|
constructor(storageSchema, initialData) {
|
||||||
|
super(storageSchema, true, initialData);
|
||||||
|
this.executor = new TriggerExecutor_1.TriggerExecutor();
|
||||||
|
}
|
||||||
|
async operate(entity, operation, context, params) {
|
||||||
|
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(entity, selection, context, params) {
|
||||||
|
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(entity, selection, context, params) {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
registerChecker(checker) {
|
||||||
|
this.executor.registerChecker(checker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CacheStore = CacheStore;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||||
|
import { RuntimeContext } from '../types/RuntimeContext';
|
||||||
|
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
||||||
|
import { Schema as Application } from '../base-ed/Application/Schema';
|
||||||
|
import { Schema as Token } from '../base-ed/Token/Schema';
|
||||||
|
import { Context, TreeStore } from 'oak-memory-tree-store';
|
||||||
|
export declare 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: TreeStore<ED>, application?: Pick<Application, 'id'>, token?: Pick<Token, 'id' | 'userId' | 'playerId'>);
|
||||||
|
on(event: "commit" | "rollback", callback: (context: any) => Promise<void>): void;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.CacheContext = void 0;
|
||||||
|
const oak_memory_tree_store_1 = require("oak-memory-tree-store");
|
||||||
|
class CacheContext extends oak_memory_tree_store_1.Context {
|
||||||
|
getApplication;
|
||||||
|
getToken;
|
||||||
|
constructor(store, application, token) {
|
||||||
|
super(store);
|
||||||
|
this.getApplication = () => application;
|
||||||
|
this.getToken = () => token;
|
||||||
|
}
|
||||||
|
on(event, callback) {
|
||||||
|
throw new Error('disallow cross txn events in FrontContext');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CacheContext = CacheContext;
|
||||||
|
;
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { Checker } from "oak-domain/lib/types";
|
||||||
|
import { EntityDict } from '../base-ed/EntityDict';
|
||||||
|
declare const checkers: Checker<EntityDict, 'address'>[];
|
||||||
|
export default checkers;
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const validator_1 = require("oak-domain/lib/utils/validator");
|
||||||
|
const types_1 = require("oak-domain/lib/types");
|
||||||
|
const check_1 = require("../utils/check");
|
||||||
|
const checkers = [
|
||||||
|
{
|
||||||
|
action: 'create',
|
||||||
|
entity: 'address',
|
||||||
|
checker: async ({ operation }) => {
|
||||||
|
const { action, data } = operation;
|
||||||
|
if (data instanceof Array) {
|
||||||
|
data.forEach(ele => {
|
||||||
|
(0, check_1.checkAttributesNotNull)(ele, ['name', 'detail', 'phone', 'areaId']);
|
||||||
|
if (!(0, validator_1.isMobile)(ele.phone)) {
|
||||||
|
throw new types_1.AttrIllegalError(['phone'], '手机号非法');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(0, check_1.checkAttributesNotNull)(data, ['name', 'detail', 'phone', 'areaId']);
|
||||||
|
if (!(0, validator_1.isMobile)(data.phone)) {
|
||||||
|
throw new types_1.AttrIllegalError(['phone'], '手机号非法');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
|
exports.default = checkers;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
declare const _default: import("oak-domain/lib/types").Checker<import("../base-ed/EntityDict").EntityDict, "address">[];
|
||||||
|
export default _default;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const address_1 = __importDefault(require("./address"));
|
||||||
|
exports.default = [...address_1.default];
|
||||||
|
|
@ -1,2 +1,35 @@
|
||||||
declare const area: any[];
|
declare const area: ({
|
||||||
|
code: string;
|
||||||
|
level: string;
|
||||||
|
parentId: null;
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
id: string;
|
||||||
|
center: {
|
||||||
|
type: string;
|
||||||
|
coordinate: number[];
|
||||||
|
};
|
||||||
|
} | {
|
||||||
|
code: string;
|
||||||
|
level: string;
|
||||||
|
parentId: string;
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
id: string;
|
||||||
|
center: {
|
||||||
|
type: string;
|
||||||
|
coordinate: number[];
|
||||||
|
};
|
||||||
|
} | {
|
||||||
|
code: number;
|
||||||
|
level: string;
|
||||||
|
parentId: string;
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
id: number;
|
||||||
|
center: {
|
||||||
|
type: string;
|
||||||
|
coordinate: number[];
|
||||||
|
};
|
||||||
|
})[];
|
||||||
export { area, };
|
export { area, };
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.area = void 0;
|
exports.area = void 0;
|
||||||
const area_json_1 = __importDefault(require("./area.json"));
|
// import area2 from './area.json';
|
||||||
const area_debug_json_1 = __importDefault(require("./area-debug.json"));
|
const area_debug_json_1 = __importDefault(require("./area-debug.json"));
|
||||||
const area = process.env.NODE_ENV === 'production' ? area_json_1.default : area_debug_json_1.default;
|
const area = area_debug_json_1.default;
|
||||||
exports.area = area;
|
exports.area = area;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,39 @@ declare const _default: {
|
||||||
userRole: import("../base-ed/UserRole/Schema").CreateOperationData[];
|
userRole: import("../base-ed/UserRole/Schema").CreateOperationData[];
|
||||||
user: import("../base-ed/User/Schema").CreateOperationData[];
|
user: import("../base-ed/User/Schema").CreateOperationData[];
|
||||||
role: import("../base-ed/Role/Schema").CreateOperationData[];
|
role: import("../base-ed/Role/Schema").CreateOperationData[];
|
||||||
area: any[];
|
area: ({
|
||||||
|
code: string;
|
||||||
|
level: string;
|
||||||
|
parentId: null;
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
id: string;
|
||||||
|
center: {
|
||||||
|
type: string;
|
||||||
|
coordinate: number[];
|
||||||
|
};
|
||||||
|
} | {
|
||||||
|
code: string;
|
||||||
|
level: string;
|
||||||
|
parentId: string;
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
id: string;
|
||||||
|
center: {
|
||||||
|
type: string;
|
||||||
|
coordinate: number[];
|
||||||
|
};
|
||||||
|
} | {
|
||||||
|
code: number;
|
||||||
|
level: string;
|
||||||
|
parentId: string;
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
id: number;
|
||||||
|
center: {
|
||||||
|
type: string;
|
||||||
|
coordinate: number[];
|
||||||
|
};
|
||||||
|
})[];
|
||||||
};
|
};
|
||||||
export default _default;
|
export default _default;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import aspectDict from "./aspects";
|
import aspectDict from "./aspects";
|
||||||
import triggers from "./triggers";
|
import triggers from "./triggers";
|
||||||
|
import checkers from './checkers';
|
||||||
import data from "./data";
|
import data from "./data";
|
||||||
export { triggers, data, aspectDict, };
|
export { checkers, triggers, data, aspectDict, };
|
||||||
|
export * from './types/RuntimeContext';
|
||||||
|
|
|
||||||
19
lib/index.js
19
lib/index.js
|
|
@ -1,12 +1,29 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||||
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||||
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.aspectDict = exports.data = exports.triggers = void 0;
|
exports.aspectDict = exports.data = exports.triggers = exports.checkers = void 0;
|
||||||
const aspects_1 = __importDefault(require("./aspects"));
|
const aspects_1 = __importDefault(require("./aspects"));
|
||||||
exports.aspectDict = aspects_1.default;
|
exports.aspectDict = aspects_1.default;
|
||||||
const triggers_1 = __importDefault(require("./triggers"));
|
const triggers_1 = __importDefault(require("./triggers"));
|
||||||
exports.triggers = triggers_1.default;
|
exports.triggers = triggers_1.default;
|
||||||
|
const checkers_1 = __importDefault(require("./checkers"));
|
||||||
|
exports.checkers = checkers_1.default;
|
||||||
const data_1 = __importDefault(require("./data"));
|
const data_1 = __importDefault(require("./data"));
|
||||||
exports.data = data_1.default;
|
exports.data = data_1.default;
|
||||||
|
__exportStar(require("./types/RuntimeContext"), exports);
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
export {};
|
import { CreateTriggerInTxn } from 'oak-domain/lib/types/Trigger';
|
||||||
|
import { EntityDict } from '../base-ed/EntityDict';
|
||||||
|
declare const triggers: CreateTriggerInTxn<EntityDict, 'address'>[];
|
||||||
|
export default triggers;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,4 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const triggers = [
|
const triggers = [];
|
||||||
{
|
exports.default = triggers;
|
||||||
entity: 'address',
|
|
||||||
action: 'create',
|
|
||||||
when: 'before',
|
|
||||||
fn: async ({ operation }, context) => {
|
|
||||||
const { action, data } = operation;
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
name: '建立新area前检查数据',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
declare const _default: never[];
|
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||||
|
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
||||||
|
import { Trigger } from 'oak-domain/lib/types';
|
||||||
|
declare const _default: Trigger<EntityDict & BaseEntityDict, string | number>[];
|
||||||
export default _default;
|
export default _default;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.default = [];
|
const address_1 = __importDefault(require("./address"));
|
||||||
|
exports.default = [...address_1.default];
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||||
import { Schema as Application } from '../base-ed/Application/Schema';
|
import { Schema as Application } from '../base-ed/Application/Schema';
|
||||||
import { Schema as Token } from '../base-ed/Token/Schema';
|
import { Schema as Token } from '../base-ed/Token/Schema';
|
||||||
export interface RuntimeContext<ED extends EntityDict> extends Context<ED> {
|
export interface RuntimeContext<ED extends EntityDict> extends Context<ED> {
|
||||||
getApplication: () => Application;
|
getApplication: () => Pick<Application, 'id'> | undefined;
|
||||||
getToken: () => Token | undefined;
|
getToken: () => Pick<Token, 'id' | 'userId' | 'playerId'> | undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export declare function checkAttributesNotNull<T extends Record<string, any>>(data: T, attributes: Array<keyof T>, allowEmpty?: true): void;
|
||||||
|
export declare function checkAttributesScope<T extends Record<string, any>>(data: T, attributes: Array<keyof T>): void;
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.checkAttributesScope = exports.checkAttributesNotNull = void 0;
|
||||||
|
const types_1 = require("oak-domain/lib/types");
|
||||||
|
function checkAttributesNotNull(data, attributes, allowEmpty) {
|
||||||
|
const attrs = attributes.filter((attr) => {
|
||||||
|
if (data[attr] === null || data[attr] === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!allowEmpty && !data.hasOwnProperty(attr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (attrs.length > 0) {
|
||||||
|
throw new types_1.AttrIllegalError(attrs, '属性不能为空');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.checkAttributesNotNull = checkAttributesNotNull;
|
||||||
|
;
|
||||||
|
function checkAttributesScope(data, attributes) {
|
||||||
|
const attrs = attributes.filter(attr => !data.hasOwnProperty(attr));
|
||||||
|
if (attrs.length > 0) {
|
||||||
|
throw new types_1.AttrIllegalError(attrs, '多余的属性');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.checkAttributesScope = checkAttributesScope;
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "oak框架中公共业务逻辑的实现",
|
"description": "oak框架中公共业务逻辑的实现",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21",
|
||||||
|
"oak-domain": "file:../oak-domain",
|
||||||
|
"oak-frontend-base": "file:../oak-frontend-base",
|
||||||
|
"oak-memory-tree-store": "file:../oak-memory-tree-store"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.13",
|
"@babel/cli": "^7.12.13",
|
||||||
|
|
@ -24,7 +27,6 @@
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mocha": "^8.2.1",
|
"mocha": "^8.2.1",
|
||||||
"oak-domain": "file:../oak-domain",
|
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.5.2"
|
"typescript": "^4.5.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
import { Context } from 'oak-domain/lib/types';
|
||||||
|
import { UniversalContext } from 'oak-domain/lib/store/UniversalContext';
|
||||||
|
import { EntityDict } from './base-ed/EntityDict';
|
||||||
|
import { RowStore } from 'oak-domain/lib/types';
|
||||||
|
|
||||||
|
|
||||||
|
export class RuntimeContext<ED extends EntityDict> extends UniversalContext<ED> implements Context<ED> {
|
||||||
|
applicationId: string;
|
||||||
|
constructor(store: RowStore<ED>, appId: string) {
|
||||||
|
super(store);
|
||||||
|
this.applicationId = appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplication () {
|
||||||
|
return this.rowStore.select('application', {
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
name: 1,
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
id: this.applicationId,
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import { EntityDict, OperateParams } from 'oak-domain/lib/types/Entity';
|
import { OperateParams, EntityDict, OperationResult, SelectionResult2 } from 'oak-domain/lib/types/Entity';
|
||||||
import { RuntimeContext } from '../types/RuntimeContext';
|
import { RuntimeContext } from '../RuntimeContext';
|
||||||
|
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
||||||
|
|
||||||
export async function operate<ED extends EntityDict, T extends keyof ED>(
|
export async function operate<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(
|
||||||
options: { entity: T, operation: ED[T]['Operation'], params?: OperateParams }, context: RuntimeContext<ED>) {
|
options: { entity: T, operation: ED[T]['Operation'], params?: OperateParams }, context: RuntimeContext<ED>) {
|
||||||
const { entity, operation, params } = options;
|
const { entity, operation, params } = options;
|
||||||
return context.rowStore.operate(entity, operation, context, params);
|
return context.rowStore.operate(entity, operation, context, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function select<ED extends EntityDict, T extends keyof ED>(
|
export async function select<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(
|
||||||
options: { entity: T, selection: ED[T]['Selection'], params?: object }, context: RuntimeContext<ED>) {
|
options: { entity: T, selection: ED[T]['Selection'], params?: object }, context: RuntimeContext<ED>) {
|
||||||
const { entity, selection, params } = options;
|
const { entity, selection, params } = options;
|
||||||
return context.rowStore.select(entity, selection, context, params);
|
return context.rowStore.select(entity, selection, context, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
export type AspectDict<ED extends EntityDict & BaseEntityDict> = {
|
||||||
|
operation: <T extends keyof ED>(options: { entity: T, operation: ED[T]['Operation'], params?: OperateParams }, context: RuntimeContext<ED>) => Promise<OperationResult>;
|
||||||
|
select: <T extends keyof ED, S extends ED[T]['Selection']>( options: { entity: T, selection: S, params?: object }, context: RuntimeContext<ED>) => Promise<SelectionResult2<ED[T]['Schema'], S>>;
|
||||||
|
}; */
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { loginByPassword, loginMp } from './token';
|
import { EntityDict } from 'oak-domain/lib/types';
|
||||||
import { operate, select } from './crud';
|
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
||||||
|
import { loginByPassword, loginMp, /* AspectDict as TokenAD */} from './token';
|
||||||
|
import { operate, select, /* AspectDict as CrudAD */ } from './crud';
|
||||||
|
|
||||||
const aspectDict = {
|
const aspectDict = {
|
||||||
loginByPassword,
|
loginByPassword,
|
||||||
|
|
@ -8,5 +10,6 @@ const aspectDict = {
|
||||||
select,
|
select,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default aspectDict;
|
||||||
|
|
||||||
export default aspectDict;
|
// export type AspectDict<ED extends EntityDict & BaseEntityDict> = TokenAD<ED> & CrudAD<ED>;
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
import { RuntimeContext } from '../types/RuntimeContext';
|
import { RuntimeContext } from '../RuntimeContext';
|
||||||
import { Schema as Token } from '../base-ed/Token/Schema';
|
import { EntityDict } from '../base-ed/EntityDict';
|
||||||
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
|
||||||
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
|
||||||
|
|
||||||
export async function loginMp(params: { code: string }, context: RuntimeContext<BaseEntityDict>): Promise<string> {
|
export async function loginMp<ED extends EntityDict>(params: { code: string }, context: RuntimeContext<ED>): Promise<string> {
|
||||||
const { rowStore } = context;
|
const { rowStore } = context;
|
||||||
throw new Error('method not implemented!');
|
throw new Error('method not implemented!');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginByPassword(params: { password: string, mobile: string }, context: RuntimeContext<BaseEntityDict>): Promise<string> {
|
export async function loginByPassword<ED extends EntityDict>(params: { password: string, mobile: string }, context: RuntimeContext<ED>): Promise<string> {
|
||||||
const { rowStore } = context;
|
const { rowStore } = context;
|
||||||
throw new Error('method not implemented!');
|
throw new Error('method not implemented!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* export type AspectDict<ED extends EntityDict> = {
|
||||||
|
loginMp: (params: { code: string }, context: RuntimeContext<ED>) => Promise<string>;
|
||||||
|
loginByPassword: (params: { password: string, mobile: string }, context: RuntimeContext<ED>) => Promise<string>;
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { isMobile } from 'oak-domain/lib/utils/validator';
|
import { isMobile } from 'oak-domain/lib/utils/validator';
|
||||||
import { AttrIllegalError, Checker } from "../types/Auth";
|
import { AttrIllegalError, Checker, CreateChecker, DeduceCreateOperation, InstinctiveAttributes } from "oak-domain/lib/types";
|
||||||
import { EntityDict } from '../base-ed/EntityDict';
|
import { EntityDict } from '../base-ed/EntityDict';
|
||||||
import { checkAttributesNotNull } from '../utils/check';
|
import { checkAttributesNotNull } from '../utils/check';
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ const checkers: Checker<EntityDict, 'address'> [] = [
|
||||||
if (data instanceof Array) {
|
if (data instanceof Array) {
|
||||||
data.forEach(
|
data.forEach(
|
||||||
ele => {
|
ele => {
|
||||||
|
const a: Exclude<keyof EntityDict['address']['OpSchema'], 'aa'> = 'name';
|
||||||
checkAttributesNotNull(ele, ['name', 'detail', 'phone', 'areaId']);
|
checkAttributesNotNull(ele, ['name', 'detail', 'phone', 'areaId']);
|
||||||
if (!isMobile(ele.phone)) {
|
if (!isMobile(ele.phone)) {
|
||||||
throw new AttrIllegalError(['phone'], '手机号非法');
|
throw new AttrIllegalError(['phone'], '手机号非法');
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import aspectDict from "./aspects";
|
import aspectDict/* , { AspectDict } */ from "./aspects";
|
||||||
import triggers from "./triggers";
|
import triggers from "./triggers";
|
||||||
import checkers from './checkers';
|
import checkers from './checkers';
|
||||||
import data from "./data";
|
import data from "./data";
|
||||||
|
|
@ -8,10 +8,7 @@ export {
|
||||||
triggers,
|
triggers,
|
||||||
data,
|
data,
|
||||||
aspectDict,
|
aspectDict,
|
||||||
|
/* AspectDict, */
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from './types/Trigger';
|
export * from './RuntimeContext';
|
||||||
export * from './types/Aspect';
|
|
||||||
export * from './utils/TriggerExecutor';
|
|
||||||
export * from './types/RuntimeContext';
|
|
||||||
export * from './types/Auth';
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { CreateTriggerInTxn, Trigger } from '../types/Trigger';
|
import { CreateTriggerInTxn, Trigger } from 'oak-domain/lib/types/Trigger';
|
||||||
import { EntityDict } from '../base-ed/EntityDict';
|
import { EntityDict } from '../base-ed/EntityDict';
|
||||||
|
|
||||||
const triggers: CreateTriggerInTxn<EntityDict, 'address'>[] = [
|
|
||||||
|
const triggers: Trigger<EntityDict, 'address'>[] = [
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default triggers;
|
export default triggers;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
||||||
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
import { EntityDict as BaseEntityDict } from '../base-ed/EntityDict';
|
||||||
import { Trigger } from '../types/Trigger';
|
import { Trigger } from 'oak-domain/lib/types';
|
||||||
import addressTriggers from './address';
|
import addressTriggers from './address';
|
||||||
|
|
||||||
export default [...addressTriggers] as Array<Trigger<EntityDict & BaseEntityDict, keyof (EntityDict & BaseEntityDict)>>;
|
export default [...addressTriggers];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { EntityDict } from "oak-domain/lib/types/Entity";
|
|
||||||
import { RuntimeContext } from './RuntimeContext';
|
|
||||||
|
|
||||||
export interface Aspect<ED extends EntityDict>{
|
|
||||||
(params: any, context: RuntimeContext<ED>): Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import { EntityDict } from "oak-domain/lib/types/Entity";
|
|
||||||
import { CreateTriggerBase, RemoveTriggerBase, UpdateTriggerBase } from "./Trigger";
|
|
||||||
|
|
||||||
export class AttrIllegalError extends Error {
|
|
||||||
private attributes: string[];
|
|
||||||
constructor(attributes: string[], message?: string) {
|
|
||||||
super(message);
|
|
||||||
this.attributes = attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttributes() {
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type CreateChecker<ED extends EntityDict, T extends keyof ED> = {
|
|
||||||
action: 'create';
|
|
||||||
entity: T;
|
|
||||||
checker: CreateTriggerBase<ED, T>['fn'],
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpdateChecker<ED extends EntityDict, T extends keyof ED> = {
|
|
||||||
action: UpdateTriggerBase<ED, T>['action'];
|
|
||||||
entity: T;
|
|
||||||
checker: UpdateTriggerBase<ED, T>['fn'],
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RemoveChecker<ED extends EntityDict, T extends keyof ED> = {
|
|
||||||
action: 'remove';
|
|
||||||
entity: T;
|
|
||||||
checker: RemoveTriggerBase<ED, T>['fn'],
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Checker<ED extends EntityDict, T extends keyof ED> = CreateChecker<ED, T> | UpdateChecker<ED, T> | RemoveChecker<ED, T>;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
import { Context } from 'oak-domain/lib/types/Context';
|
|
||||||
import { EntityDict } from 'oak-domain/lib/types/Entity';
|
|
||||||
import { Schema as Application } from '../base-ed/Application/Schema';
|
|
||||||
import { Schema as Token } from '../base-ed/Token/Schema';
|
|
||||||
|
|
||||||
|
|
||||||
export interface RuntimeContext<ED extends EntityDict> extends Context<ED> {
|
|
||||||
getApplication: () => Pick<Application, 'id'> | undefined;
|
|
||||||
getToken: () => Pick<Token, 'id' | 'userId' | 'playerId'> | undefined;
|
|
||||||
on(event: 'commit' | 'rollback', callback: (context: RuntimeContext<ED>) => Promise<void>): void;
|
|
||||||
};
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
|
||||||
import { DeduceCreateOperation, DeduceRemoveOperation, DeduceSelection, DeduceUpdateOperation, EntityDict } from "oak-domain/lib/types/Entity";
|
|
||||||
import { EntityDef, EntityShape, OperationResult, SelectionResult2, TriggerDataAttribute, TriggerTimestampAttribute } from "oak-domain/src/types/Entity";
|
|
||||||
import { RuntimeContext } from "./RuntimeContext";
|
|
||||||
|
|
||||||
export interface CreateTriggerBase<ED extends EntityDict, T extends keyof ED> {
|
|
||||||
entity: T;
|
|
||||||
name: string;
|
|
||||||
action: 'create',
|
|
||||||
check?: (operation: DeduceCreateOperation<ED[T]['Schema']>) => boolean;
|
|
||||||
fn: (event: { operation: DeduceCreateOperation<ED[T]['Schema']>; }, context: RuntimeContext<ED>, params?: Object) => Promise<number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface CreateTriggerInTxn<ED extends EntityDict, T extends keyof ED> extends CreateTriggerBase<ED, T> {
|
|
||||||
when: 'before' | 'after',
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface CreateTriggerCrossTxn<ED extends EntityDict, T extends keyof ED> extends CreateTriggerBase<ED, T> {
|
|
||||||
when: 'commit',
|
|
||||||
strict?: 'takeEasy' | 'makeSure';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CreateTrigger<ED extends EntityDict, T extends keyof ED> = CreateTriggerInTxn<ED, T> | CreateTriggerCrossTxn<ED, T>;
|
|
||||||
|
|
||||||
|
|
||||||
export interface UpdateTriggerBase<ED extends EntityDict, T extends keyof ED> {
|
|
||||||
entity: T;
|
|
||||||
name: string;
|
|
||||||
action: Exclude<ED[T]['Action'], GenericAction> | 'update',
|
|
||||||
attributes?: keyof ED[T]['OpSchema'] | Array<keyof ED[T]['OpSchema']>;
|
|
||||||
check?: (operation: DeduceUpdateOperation<ED[T]['Schema']>) => boolean;
|
|
||||||
fn: (event: { operation: DeduceUpdateOperation<ED[T]['Schema']> }, context: RuntimeContext<ED>, params?: Object) => Promise<number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface UpdateTriggerInTxn<ED extends EntityDict, T extends keyof ED> extends UpdateTriggerBase<ED, T> {
|
|
||||||
when: 'before' | 'after',
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface UpdateTriggerCrossTxn<ED extends EntityDict, T extends keyof ED> extends UpdateTriggerBase<ED, T> {
|
|
||||||
when: 'commit',
|
|
||||||
strict?: 'takeEasy' | 'makeSure';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpdateTrigger<ED extends EntityDict, T extends keyof ED> = UpdateTriggerInTxn<ED, T> | UpdateTriggerCrossTxn<ED, T>;
|
|
||||||
|
|
||||||
|
|
||||||
export interface RemoveTriggerBase<ED extends EntityDict, T extends keyof ED> {
|
|
||||||
entity: T;
|
|
||||||
name: string;
|
|
||||||
action: 'remove',
|
|
||||||
check?: (operation: DeduceRemoveOperation<ED[T]['Schema']>) => boolean;
|
|
||||||
fn: (event: { operation: DeduceRemoveOperation<ED[T]['Schema']> }, context: RuntimeContext<ED>, params?: Object) => Promise<number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RemoveTriggerInTxn<ED extends EntityDict, T extends keyof ED> extends RemoveTriggerBase<ED, T> {
|
|
||||||
when: 'before' | 'after',
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RemoveTriggerCrossTxn<ED extends EntityDict, T extends keyof ED> extends RemoveTriggerBase<ED, T> {
|
|
||||||
when: 'commit',
|
|
||||||
strict?: 'takeEasy' | 'makeSure';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RemoveTrigger<ED extends EntityDict, T extends keyof ED> = RemoveTriggerInTxn<ED, T> | RemoveTriggerCrossTxn<ED, T>;
|
|
||||||
|
|
||||||
|
|
||||||
export interface SelectTriggerBase<ED extends EntityDict, T extends keyof ED> {
|
|
||||||
entity: T;
|
|
||||||
name: string;
|
|
||||||
action: 'select';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* selection似乎不需要支持跨事务?没想清楚
|
|
||||||
* todo by Xc
|
|
||||||
*/
|
|
||||||
export interface SelectTriggerBefore<ED extends EntityDict, T extends keyof ED> extends SelectTriggerBase<ED, T> {
|
|
||||||
when: 'before';
|
|
||||||
fn: (event: { operation: DeduceSelection<ED[T]['Schema']> }, context: RuntimeContext<ED>, params?: Object) => Promise<number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SelectTriggerAfter<ED extends EntityDict, T extends keyof ED> extends SelectTriggerBase<ED, T> {
|
|
||||||
when: 'after',
|
|
||||||
fn: <S extends ED[T]['Selection']>(event: {
|
|
||||||
operation: S;
|
|
||||||
result: SelectionResult2<ED[T]['Schema'], S['data']>;
|
|
||||||
}, context: RuntimeContext<ED>, params?: Object) => Promise<number>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SelectTrigger<ED extends EntityDict, T extends keyof ED> = SelectTriggerBefore<ED, T> | SelectTriggerAfter<ED, T>;
|
|
||||||
|
|
||||||
export type Trigger<ED extends EntityDict, T extends keyof ED> = CreateTrigger<ED, T> | UpdateTrigger<ED, T> | RemoveTrigger<ED, T> | SelectTrigger<ED, T>;
|
|
||||||
|
|
||||||
export interface TriggerEntityShape extends EntityShape {
|
|
||||||
$$triggerData$$?: {
|
|
||||||
name: string;
|
|
||||||
operation: object;
|
|
||||||
};
|
|
||||||
$$triggerTimestamp$$?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class Executor<ED extends EntityDict> {
|
|
||||||
static dataAttr: TriggerDataAttribute = '$$triggerData$$';
|
|
||||||
static timestampAttr: TriggerTimestampAttribute = '$$triggerTimestamp$$';
|
|
||||||
|
|
||||||
abstract registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T>): void;
|
|
||||||
|
|
||||||
abstract preOperation<T extends keyof ED>(
|
|
||||||
entity: T,
|
|
||||||
operation: ED[T]['Operation'],
|
|
||||||
context: RuntimeContext<ED>
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
abstract postOperation<T extends keyof ED>(
|
|
||||||
entity: T,
|
|
||||||
operation: ED[T]['Operation'],
|
|
||||||
context: RuntimeContext<ED>
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
abstract checkpoint(context: RuntimeContext<ED>, timestamp: number): Promise<number>; // 将所有在timestamp之前存在不一致的数据进行恢复
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { EntityDict } from "oak-domain/lib/types/Entity";
|
|
||||||
import { Logger } from "oak-domain/lib/types/Logger";
|
|
||||||
import { Checker } from '../types/Auth';
|
|
||||||
import { RuntimeContext } from '../types/RuntimeContext';
|
|
||||||
import { Trigger, Executor } from "../types/Trigger";
|
|
||||||
export declare class TriggerExecutor<ED extends EntityDict> extends Executor<ED> {
|
|
||||||
private triggerMap;
|
|
||||||
private triggerNameMap;
|
|
||||||
private volatileEntities;
|
|
||||||
private logger;
|
|
||||||
constructor(logger?: Logger);
|
|
||||||
registerChecker<T extends keyof ED>(checker: Checker<ED, T>): void;
|
|
||||||
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T>): void;
|
|
||||||
private preCommitTrigger;
|
|
||||||
preOperation<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: RuntimeContext<ED>): Promise<void>;
|
|
||||||
private onCommit;
|
|
||||||
private postCommitTrigger;
|
|
||||||
postOperation<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: RuntimeContext<ED>): Promise<void>;
|
|
||||||
checkpoint(context: RuntimeContext<ED>, timestamp: number): Promise<number>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.TriggerExecutor = void 0;
|
|
||||||
const assert_1 = __importDefault(require("assert"));
|
|
||||||
const lodash_1 = require("lodash");
|
|
||||||
const filter_1 = require("oak-domain/lib/store/filter");
|
|
||||||
const Trigger_1 = require("../types/Trigger");
|
|
||||||
class TriggerExecutor extends Trigger_1.Executor {
|
|
||||||
triggerMap;
|
|
||||||
triggerNameMap;
|
|
||||||
volatileEntities;
|
|
||||||
logger;
|
|
||||||
constructor(logger = console) {
|
|
||||||
super();
|
|
||||||
this.logger = logger;
|
|
||||||
this.triggerMap = {};
|
|
||||||
this.triggerNameMap = {};
|
|
||||||
this.volatileEntities = [];
|
|
||||||
}
|
|
||||||
registerChecker(checker) {
|
|
||||||
const { entity, action, checker: checkFn } = checker;
|
|
||||||
const ActionNameMatrix = {
|
|
||||||
'create': '创建',
|
|
||||||
'remove': '删除',
|
|
||||||
};
|
|
||||||
let triggerAction = ActionNameMatrix[action] || '更新';
|
|
||||||
const triggerName = `${entity}${triggerAction}权限检查`;
|
|
||||||
const trigger = {
|
|
||||||
name: triggerName,
|
|
||||||
entity,
|
|
||||||
action,
|
|
||||||
fn: checkFn,
|
|
||||||
when: 'before',
|
|
||||||
};
|
|
||||||
this.registerTrigger(trigger);
|
|
||||||
}
|
|
||||||
registerTrigger(trigger) {
|
|
||||||
// trigger的两种访问方式: by name, by entity/action
|
|
||||||
if (this.triggerNameMap.hasOwnProperty(trigger.name)) {
|
|
||||||
throw new Error(`不可有同名的触发器「${trigger.name}」`);
|
|
||||||
}
|
|
||||||
(0, lodash_1.assign)(this.triggerNameMap, {
|
|
||||||
[trigger.name]: trigger,
|
|
||||||
});
|
|
||||||
const triggers = this.triggerMap[trigger.entity] && this.triggerMap[trigger.entity][trigger.action];
|
|
||||||
if (triggers) {
|
|
||||||
triggers.push(trigger);
|
|
||||||
}
|
|
||||||
else if (this.triggerMap[trigger.entity]) {
|
|
||||||
(0, lodash_1.assign)(this.triggerMap[trigger.entity], {
|
|
||||||
[trigger.action]: [trigger],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
(0, lodash_1.assign)(this.triggerMap, {
|
|
||||||
[trigger.entity]: {
|
|
||||||
[trigger.action]: [trigger],
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (trigger.when === 'commit' && trigger.strict === 'makeSure') {
|
|
||||||
if (this.volatileEntities.indexOf(trigger.entity) === -1) {
|
|
||||||
this.volatileEntities.push(trigger.entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async preCommitTrigger(entity, operation, trigger, context) {
|
|
||||||
(0, assert_1.default)(trigger.action !== 'select');
|
|
||||||
if (trigger.strict === 'makeSure') {
|
|
||||||
switch (operation.action) {
|
|
||||||
case 'create': {
|
|
||||||
if (operation.data.hasOwnProperty(Trigger_1.Executor.dataAttr) || operation.data.hasOwnProperty(Trigger_1.Executor.timestampAttr)) {
|
|
||||||
throw new Error('同一行数据上不能存在两个跨事务约束');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
const { filter } = operation;
|
|
||||||
// 此时要保证更新或者删除的行上没有跨事务约束
|
|
||||||
const filter2 = (0, filter_1.addFilterSegment)({
|
|
||||||
$or: [
|
|
||||||
{
|
|
||||||
$$triggerData$$: {
|
|
||||||
$exists: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$$triggerTimestamp$$: {
|
|
||||||
$exists: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}, filter);
|
|
||||||
const { rowStore } = context;
|
|
||||||
const count = await rowStore.count(entity, {
|
|
||||||
filter: filter2
|
|
||||||
}, context);
|
|
||||||
if (count > 0) {
|
|
||||||
throw new Error(`对象${entity}的行「${JSON.stringify(operation)}」上已经存在未完成的跨事务约束`);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(0, lodash_1.assign)(operation.data, {
|
|
||||||
[Trigger_1.Executor.dataAttr]: {
|
|
||||||
name: trigger.name,
|
|
||||||
operation,
|
|
||||||
},
|
|
||||||
[Trigger_1.Executor.timestampAttr]: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async preOperation(entity, operation, context) {
|
|
||||||
const triggers = this.triggerMap[entity] && this.triggerMap[entity][operation.action];
|
|
||||||
if (triggers) {
|
|
||||||
const preTriggers = triggers.filter(ele => ele.when === 'before' && (!ele.check || ele.check(operation)));
|
|
||||||
for (const trigger of preTriggers) {
|
|
||||||
const number = await trigger.fn({ operation: operation }, context);
|
|
||||||
if (number > 0) {
|
|
||||||
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const commitTriggers = triggers.filter(ele => ele.when === 'commit' && (!ele.check || ele.check(operation)));
|
|
||||||
for (const trigger of commitTriggers) {
|
|
||||||
await this.preCommitTrigger(entity, operation, trigger, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onCommit(trigger, operation) {
|
|
||||||
return async (context) => {
|
|
||||||
await context.begin();
|
|
||||||
const number = await trigger.fn({
|
|
||||||
operation: operation,
|
|
||||||
}, context);
|
|
||||||
const { rowStore } = context;
|
|
||||||
if (trigger.strict === 'makeSure') {
|
|
||||||
// 如果是必须完成的trigger,在完成成功后要把trigger相关的属性置null;
|
|
||||||
let filter = {};
|
|
||||||
if (operation.action === 'create') {
|
|
||||||
filter = operation.data instanceof Array ? {
|
|
||||||
filter: {
|
|
||||||
id: {
|
|
||||||
$in: operation.data.map(ele => ele.id),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} : {
|
|
||||||
filter: {
|
|
||||||
id: operation.data.id,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (operation.filter) {
|
|
||||||
(0, lodash_1.assign)(filter, { filter: operation.filter });
|
|
||||||
}
|
|
||||||
await rowStore.operate(trigger.entity, {
|
|
||||||
action: 'update',
|
|
||||||
data: {
|
|
||||||
$$triggerTimestamp$$: null,
|
|
||||||
$$triggerData$$: null,
|
|
||||||
},
|
|
||||||
...filter /** as Filter<'update', DeduceFilter<ED[T]['Schema']>> */,
|
|
||||||
}, context);
|
|
||||||
}
|
|
||||||
await context.commit();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async postCommitTrigger(operation, trigger, context) {
|
|
||||||
context.on('commit', this.onCommit(trigger, operation));
|
|
||||||
}
|
|
||||||
async postOperation(entity, operation, context) {
|
|
||||||
const triggers = this.triggerMap[entity] && this.triggerMap[entity][operation.action];
|
|
||||||
if (triggers) {
|
|
||||||
const postTriggers = triggers.filter(ele => ele.when === 'after' && (!ele.check || ele.check(operation)));
|
|
||||||
for (const trigger of postTriggers) {
|
|
||||||
const number = await trigger.fn({ operation: operation }, context);
|
|
||||||
if (number > 0) {
|
|
||||||
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const commitTriggers = triggers.filter(ele => ele.when === 'commit' && (!ele.check || ele.check(operation)));
|
|
||||||
for (const trigger of commitTriggers) {
|
|
||||||
await this.postCommitTrigger(operation, trigger, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async checkpoint(context, timestamp) {
|
|
||||||
let result = 0;
|
|
||||||
const { rowStore } = context;
|
|
||||||
for (const entity of this.volatileEntities) {
|
|
||||||
const { result: rows } = await rowStore.select(entity, {
|
|
||||||
data: {
|
|
||||||
id: 1,
|
|
||||||
$$triggerData$$: 1,
|
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
$$triggerTimestamp$$: {
|
|
||||||
$gt: timestamp,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, context);
|
|
||||||
for (const row of rows) {
|
|
||||||
const { $$triggerData$$ } = row;
|
|
||||||
const { name, operation } = $$triggerData$$;
|
|
||||||
const trigger = this.triggerNameMap[name];
|
|
||||||
await this.onCommit(trigger, operation)(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.TriggerExecutor = TriggerExecutor;
|
|
||||||
|
|
@ -1,268 +0,0 @@
|
||||||
import assert from 'assert';
|
|
||||||
import { assign } from "lodash";
|
|
||||||
import { addFilterSegment } from "oak-domain/lib/store/filter";
|
|
||||||
import { DeduceCreateOperation, DeduceCreateOperationData, EntityDict } from "oak-domain/lib/types/Entity";
|
|
||||||
import { Logger } from "oak-domain/lib/types/Logger";
|
|
||||||
import { Checker } from '../types/Auth';
|
|
||||||
import { RuntimeContext } from '../types/RuntimeContext';
|
|
||||||
import { Trigger, Executor, CreateTriggerCrossTxn, CreateTrigger, CreateTriggerInTxn } from "../types/Trigger";
|
|
||||||
|
|
||||||
export class TriggerExecutor<ED extends EntityDict> extends Executor<ED> {
|
|
||||||
private triggerMap: {
|
|
||||||
[T in keyof ED]?: {
|
|
||||||
[A: string]: Array<Trigger<ED, T>>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
private triggerNameMap: {
|
|
||||||
[N: string]: Trigger<ED, keyof ED>;
|
|
||||||
};
|
|
||||||
private volatileEntities: Array<keyof ED>;
|
|
||||||
|
|
||||||
private logger: Logger;
|
|
||||||
|
|
||||||
constructor(logger: Logger = console) {
|
|
||||||
super();
|
|
||||||
this.logger = logger;
|
|
||||||
this.triggerMap = {};
|
|
||||||
this.triggerNameMap = {};
|
|
||||||
this.volatileEntities = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
registerChecker<T extends keyof ED>(checker: Checker<ED, T>): void {
|
|
||||||
const { entity, action, checker: checkFn } = checker;
|
|
||||||
const ActionNameMatrix: Record<string, string> = {
|
|
||||||
'create': '创建',
|
|
||||||
'remove': '删除',
|
|
||||||
};
|
|
||||||
let triggerAction = ActionNameMatrix[action] || '更新';
|
|
||||||
const triggerName = `${entity}${triggerAction}权限检查`;
|
|
||||||
|
|
||||||
const trigger = {
|
|
||||||
name: triggerName,
|
|
||||||
entity,
|
|
||||||
action,
|
|
||||||
fn: checkFn,
|
|
||||||
when: 'before',
|
|
||||||
} as CreateTriggerInTxn<ED, T>;
|
|
||||||
this.registerTrigger(trigger);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T>): void {
|
|
||||||
// trigger的两种访问方式: by name, by entity/action
|
|
||||||
if (this.triggerNameMap.hasOwnProperty(trigger.name)) {
|
|
||||||
throw new Error(`不可有同名的触发器「${trigger.name}」`);
|
|
||||||
}
|
|
||||||
assign(this.triggerNameMap, {
|
|
||||||
[trigger.name]: trigger,
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggers = this.triggerMap[trigger.entity] && this.triggerMap[trigger.entity]![trigger.action];
|
|
||||||
if (triggers) {
|
|
||||||
triggers.push(trigger);
|
|
||||||
}
|
|
||||||
else if (this.triggerMap[trigger.entity]) {
|
|
||||||
assign(this.triggerMap[trigger.entity], {
|
|
||||||
[trigger.action]: [trigger],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
assign(this.triggerMap, {
|
|
||||||
[trigger.entity]: {
|
|
||||||
[trigger.action]: [trigger],
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trigger.when === 'commit' && trigger.strict === 'makeSure') {
|
|
||||||
if (this.volatileEntities.indexOf(trigger.entity) === -1) {
|
|
||||||
this.volatileEntities.push(trigger.entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async preCommitTrigger<T extends keyof ED>(
|
|
||||||
entity: T,
|
|
||||||
operation: ED[T]['Operation'],
|
|
||||||
trigger: Trigger<ED, T>,
|
|
||||||
context: RuntimeContext<ED>,
|
|
||||||
) {
|
|
||||||
assert(trigger.action !== 'select');
|
|
||||||
if ((trigger as CreateTriggerCrossTxn<ED, T>).strict === 'makeSure') {
|
|
||||||
switch (operation.action) {
|
|
||||||
case 'create': {
|
|
||||||
if (operation.data.hasOwnProperty(Executor.dataAttr) || operation.data.hasOwnProperty(Executor.timestampAttr)) {
|
|
||||||
throw new Error('同一行数据上不能存在两个跨事务约束');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
const { filter } = operation;
|
|
||||||
// 此时要保证更新或者删除的行上没有跨事务约束
|
|
||||||
const filter2 = addFilterSegment({
|
|
||||||
$or: [
|
|
||||||
{
|
|
||||||
$$triggerData$$: {
|
|
||||||
$exists: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$$triggerTimestamp$$: {
|
|
||||||
$exists: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}, filter);
|
|
||||||
const { rowStore } = context;
|
|
||||||
const count = await rowStore.count(entity, {
|
|
||||||
filter: filter2
|
|
||||||
} as Omit<ED[T]['Selection'], 'action' | 'sorter' | 'data'>, context);
|
|
||||||
if (count > 0) {
|
|
||||||
throw new Error(`对象${entity}的行「${JSON.stringify(operation)}」上已经存在未完成的跨事务约束`);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assign(operation.data, {
|
|
||||||
[Executor.dataAttr]: {
|
|
||||||
name: trigger.name,
|
|
||||||
operation,
|
|
||||||
},
|
|
||||||
[Executor.timestampAttr]: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async preOperation<T extends keyof ED>(
|
|
||||||
entity: T,
|
|
||||||
operation: ED[T]['Operation'],
|
|
||||||
context: RuntimeContext<ED>
|
|
||||||
): Promise<void> {
|
|
||||||
const triggers = this.triggerMap[entity] && this.triggerMap[entity]![operation.action];
|
|
||||||
if (triggers) {
|
|
||||||
const preTriggers = triggers.filter(
|
|
||||||
ele => ele.when === 'before' && (!(ele as CreateTrigger<ED, T>).check || (ele as CreateTrigger<ED, T>).check!(operation as DeduceCreateOperation<ED[T]['Schema']>))
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const trigger of preTriggers) {
|
|
||||||
const number = await (trigger as CreateTrigger<ED, T>).fn({ operation: operation as DeduceCreateOperation<ED[T]['Schema']> }, context);
|
|
||||||
if (number > 0) {
|
|
||||||
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const commitTriggers = triggers.filter(
|
|
||||||
ele => ele.when === 'commit' && (!(ele as CreateTrigger<ED, T>).check || (ele as CreateTrigger<ED, T>).check!(operation as DeduceCreateOperation<ED[T]['Schema']>))
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const trigger of commitTriggers) {
|
|
||||||
await this.preCommitTrigger(entity, operation, trigger, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onCommit<T extends keyof ED>(
|
|
||||||
trigger: Trigger<ED, T>, operation: ED[T]['Operation']) {
|
|
||||||
return async (context: RuntimeContext<ED>) => {
|
|
||||||
await context.begin();
|
|
||||||
const number = await (trigger as CreateTrigger<ED, T>).fn({
|
|
||||||
operation: operation as DeduceCreateOperation<ED[T]['Schema']>,
|
|
||||||
}, context);
|
|
||||||
const { rowStore } = context;
|
|
||||||
if ((trigger as CreateTriggerCrossTxn<ED, T>).strict === 'makeSure') {
|
|
||||||
// 如果是必须完成的trigger,在完成成功后要把trigger相关的属性置null;
|
|
||||||
let filter = {};
|
|
||||||
if (operation.action === 'create') {
|
|
||||||
filter = operation.data instanceof Array ? {
|
|
||||||
filter: {
|
|
||||||
id: {
|
|
||||||
$in: operation.data.map(ele => (ele.id as string)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} : {
|
|
||||||
filter: {
|
|
||||||
id: (operation.data.id as string),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (operation.filter) {
|
|
||||||
assign(filter, { filter: operation.filter });
|
|
||||||
}
|
|
||||||
|
|
||||||
await rowStore.operate(trigger.entity, {
|
|
||||||
action: 'update',
|
|
||||||
data: {
|
|
||||||
$$triggerTimestamp$$: null,
|
|
||||||
$$triggerData$$: null,
|
|
||||||
} as any,
|
|
||||||
...filter /** as Filter<'update', DeduceFilter<ED[T]['Schema']>> */,
|
|
||||||
}, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.commit();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async postCommitTrigger<T extends keyof ED>(
|
|
||||||
operation: ED[T]['Operation'],
|
|
||||||
trigger: Trigger<ED, T>,
|
|
||||||
context: RuntimeContext<ED>
|
|
||||||
) {
|
|
||||||
context.on('commit', this.onCommit(trigger, operation));
|
|
||||||
}
|
|
||||||
|
|
||||||
async postOperation<T extends keyof ED>(
|
|
||||||
entity: T,
|
|
||||||
operation: ED[T]['Operation'],
|
|
||||||
context: RuntimeContext<ED>
|
|
||||||
): Promise<void> {
|
|
||||||
const triggers = this.triggerMap[entity] && this.triggerMap[entity]![operation.action];
|
|
||||||
if (triggers) {
|
|
||||||
const postTriggers = triggers.filter(
|
|
||||||
ele => ele.when === 'after' && (!(ele as CreateTrigger<ED, T>).check || (ele as CreateTrigger<ED, T>).check!(operation as DeduceCreateOperation<ED[T]['Schema']>))
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const trigger of postTriggers) {
|
|
||||||
const number = await (trigger as CreateTrigger<ED, T>).fn({ operation: operation as DeduceCreateOperation<ED[T]['Schema']> }, context);
|
|
||||||
if (number > 0) {
|
|
||||||
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const commitTriggers = (<Array<CreateTrigger<ED, T>>>triggers).filter(
|
|
||||||
ele => ele.when === 'commit' && (!ele.check || ele.check(operation as DeduceCreateOperation<ED[T]['Schema']>))
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const trigger of commitTriggers) {
|
|
||||||
await this.postCommitTrigger(operation, trigger, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkpoint(context: RuntimeContext<ED>, timestamp: number): Promise<number> {
|
|
||||||
let result = 0;
|
|
||||||
const { rowStore } = context;
|
|
||||||
for (const entity of this.volatileEntities) {
|
|
||||||
const { result: rows } = await rowStore.select(entity, {
|
|
||||||
data: {
|
|
||||||
id: 1,
|
|
||||||
$$triggerData$$: 1,
|
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
$$triggerTimestamp$$: {
|
|
||||||
$gt: timestamp,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as any, context);
|
|
||||||
for (const row of rows) {
|
|
||||||
const { $$triggerData$$ } = row;
|
|
||||||
const { name, operation } = $$triggerData$$!;
|
|
||||||
const trigger = this.triggerNameMap[name];
|
|
||||||
await this.onCommit(trigger, operation as ED[typeof entity]['Operation'])(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AttrIllegalError } from "../types/Auth";
|
import { AttrIllegalError } from "oak-domain/lib/types";
|
||||||
|
|
||||||
export function checkAttributesNotNull<T extends Record<string, any>>(data: T, attributes: Array<keyof T>, allowEmpty?: true) {
|
export function checkAttributesNotNull<T extends Record<string, any>>(data: T, attributes: Array<keyof T>, allowEmpty?: true) {
|
||||||
const attrs = attributes.filter(
|
const attrs = attributes.filter(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { unset } from 'lodash';
|
||||||
|
import { buildSchema, analyzeEntities } from 'oak-domain/src/compiler/schemalBuilder';
|
||||||
|
|
||||||
|
analyzeEntities(`${__dirname}/../node_modules/oak-domain/src/entities`);
|
||||||
|
analyzeEntities(`${__dirname}/entities`);
|
||||||
|
buildSchema(`${__dirname}/app-domain`);
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { String, Int, Datetime, Image, Boolean, Text } from 'oak-domain/lib/types/DataType';
|
||||||
|
import { Schema as Area } from 'oak-domain/lib/entities/Area';
|
||||||
|
import { Schema as User } from 'oak-domain/lib/entities/User';
|
||||||
|
import { Schema as ExtraFile } from 'oak-domain/lib/entities/ExtraFile';
|
||||||
|
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||||
|
|
||||||
|
export interface Schema extends EntityShape {
|
||||||
|
district: String<16>;
|
||||||
|
area: Area;
|
||||||
|
owner: User;
|
||||||
|
dd: Array<ExtraFile>;
|
||||||
|
};
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
/* Advanced Options */
|
/* Advanced Options */
|
||||||
|
"experimentalDecorators": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"resolveJsonModule": true /* Disallow inconsistently-cased references to the same file. */
|
"resolveJsonModule": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue