Merge branch 'release'
This commit is contained in:
commit
c98a8c43ea
|
|
@ -12,7 +12,8 @@ exports.desc = {
|
|||
type: "varchar",
|
||||
params: {
|
||||
length: 32
|
||||
}
|
||||
},
|
||||
ref: ["user"]
|
||||
},
|
||||
entityId: {
|
||||
type: "varchar",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ exports.desc = {
|
|||
type: "varchar",
|
||||
params: {
|
||||
length: 32
|
||||
}
|
||||
},
|
||||
ref: ["modi", "user"]
|
||||
},
|
||||
entityId: {
|
||||
type: "varchar",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ var modi_1 = require("../store/modi");
|
|||
function createDynamicCheckers(schema, authDict) {
|
||||
var checkers = [];
|
||||
checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, modi_1.createModiRelatedCheckers)(schema)), false));
|
||||
// checkers.push(...createRemoveCheckers<ED, Cxt>(schema));
|
||||
if (authDict) {
|
||||
checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createAuthCheckers)(schema, authDict)), false));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2871,6 +2871,14 @@ function constructAttributes(entity) {
|
|||
attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("varchar")), factory.createPropertyAssignment(factory.createIdentifier("params"), factory.createObjectLiteralExpression([
|
||||
factory.createPropertyAssignment(factory.createIdentifier("length"), factory.createNumericLiteral(typeArguments[0].literal.text)),
|
||||
], true)));
|
||||
// 如果是entity,在这里处理一下ref
|
||||
if (ts.isIdentifier(name) && name.text === 'entity') {
|
||||
var mtoRelations = ReversePointerRelations[entity];
|
||||
if (mtoRelations) {
|
||||
var mtoEntities = mtoRelations.map(function (ele) { return (0, string_1.firstLetterLowerCase)(ele); });
|
||||
attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("ref"), factory.createArrayLiteralExpression(mtoEntities.map(function (ele) { return factory.createStringLiteral(ele); }), false)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Text':
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ var assert_1 = tslib_1.__importDefault(require("assert"));
|
|||
var lodash_1 = require("../utils/lodash");
|
||||
var filter_1 = require("../store/filter");
|
||||
var Entity_1 = require("../types/Entity");
|
||||
var Trigger_1 = require("../types/Trigger");
|
||||
var SyncRowStore_1 = require("./SyncRowStore");
|
||||
var checker_1 = require("./checker");
|
||||
/**
|
||||
|
|
@ -33,10 +34,11 @@ var TriggerExecutor = /** @class */ (function () {
|
|||
var entity = checker.entity, action = checker.action, type = checker.type, conditionalFilter = checker.conditionalFilter;
|
||||
var triggerName = "".concat(String(entity)).concat(action, "\u6743\u9650\u68C0\u67E5-").concat(this.counter++);
|
||||
var _a = (0, checker_1.translateCheckerInAsyncContext)(checker), fn = _a.fn, when = _a.when;
|
||||
var priority = type === 'data' ? Trigger_1.DATA_CHECKER_DEFAULT_PRIORITY : Trigger_1.CHECKER_DEFAULT_PRIORITY; // checker的默认优先级最低(前面的trigger可能会赋上一些相应的值)
|
||||
var trigger = {
|
||||
checkerType: type,
|
||||
name: triggerName,
|
||||
priority: checker.priority || 20,
|
||||
priority: checker.priority || priority,
|
||||
entity: entity,
|
||||
action: action,
|
||||
fn: fn,
|
||||
|
|
@ -59,7 +61,10 @@ var TriggerExecutor = /** @class */ (function () {
|
|||
throw new Error("\u4E0D\u53EF\u6709\u540C\u540D\u7684\u89E6\u53D1\u5668\u300C".concat(trigger.name, "\u300D"));
|
||||
}
|
||||
if (typeof trigger.priority !== 'number') {
|
||||
trigger.priority = 10; // 默认值
|
||||
trigger.priority = Trigger_1.TRIGGER_DEFAULT_PRIORITY; // 默认值
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(trigger.priority <= Trigger_1.TRIGGER_MAX_PRIORITY && trigger.priority >= Trigger_1.TRIGGER_MIN_PRIORITY, "trigger\u300C".concat(trigger.name, "\u300D\u7684\u4F18\u5148\u7EA7\u5B9A\u4E49\u8D8A\u754C\uFF0C\u5E94\u8BE5\u5728").concat(Trigger_1.TRIGGER_MIN_PRIORITY, "\u5230").concat(Trigger_1.TRIGGER_MAX_PRIORITY, "\u4E4B\u95F4"));
|
||||
}
|
||||
if (trigger.filter) {
|
||||
(0, assert_1.default)(typeof trigger.action === 'string' && trigger.action !== 'create'
|
||||
|
|
|
|||
|
|
@ -10,4 +10,17 @@ export declare function translateCheckerInSyncContext<ED extends EntityDict & Ba
|
|||
fn: (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void;
|
||||
when: 'before' | 'after';
|
||||
};
|
||||
/**
|
||||
* 根据权限定义,创建出相应的checker
|
||||
* @param schema
|
||||
* @param authDict
|
||||
* @returns
|
||||
*/
|
||||
export declare function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>, authDict: AuthDefDict<ED>): Checker<ED, keyof ED, Cxt>[];
|
||||
/**
|
||||
* 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现
|
||||
* @param schema
|
||||
* @returns
|
||||
* 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功
|
||||
*/
|
||||
export declare function createRemoveCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>): Checker<ED, keyof ED, Cxt>[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createAuthCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0;
|
||||
exports.createRemoveCheckers = exports.createAuthCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
var filter_1 = require("../store/filter");
|
||||
|
|
@ -392,6 +392,12 @@ function translateActionAuthFilterMaker(schema, relationItem, entity) {
|
|||
var filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity);
|
||||
return function (userId) { return filterMaker(userId); };
|
||||
}
|
||||
/**
|
||||
* 根据权限定义,创建出相应的checker
|
||||
* @param schema
|
||||
* @param authDict
|
||||
* @returns
|
||||
*/
|
||||
function createAuthCheckers(schema, authDict) {
|
||||
var checkers = [];
|
||||
var _loop_1 = function (entity) {
|
||||
|
|
@ -496,3 +502,224 @@ function createAuthCheckers(schema, authDict) {
|
|||
return checkers;
|
||||
}
|
||||
exports.createAuthCheckers = createAuthCheckers;
|
||||
/**
|
||||
* 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现
|
||||
* @param schema
|
||||
* @returns
|
||||
* 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功
|
||||
*/
|
||||
function createRemoveCheckers(schema) {
|
||||
var e_1, _a;
|
||||
var checkers = [];
|
||||
// 先建立所有的一对多的关系
|
||||
var OneToManyMatrix = {};
|
||||
var OneToManyOnEntityMatrix = {};
|
||||
var addToMto = function (e, f, attr) {
|
||||
var _a;
|
||||
if (OneToManyMatrix[f]) {
|
||||
(_a = OneToManyMatrix[f]) === null || _a === void 0 ? void 0 : _a.push([e, attr]);
|
||||
}
|
||||
else {
|
||||
OneToManyMatrix[f] = [[e, attr]];
|
||||
}
|
||||
};
|
||||
var addToMtoEntity = function (e, fs) {
|
||||
var e_2, _a;
|
||||
var _b;
|
||||
try {
|
||||
for (var fs_1 = tslib_1.__values(fs), fs_1_1 = fs_1.next(); !fs_1_1.done; fs_1_1 = fs_1.next()) {
|
||||
var f = fs_1_1.value;
|
||||
if (!OneToManyOnEntityMatrix[f]) {
|
||||
OneToManyOnEntityMatrix[f] = [e];
|
||||
}
|
||||
else {
|
||||
(_b = OneToManyOnEntityMatrix[f]) === null || _b === void 0 ? void 0 : _b.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (fs_1_1 && !fs_1_1.done && (_a = fs_1.return)) _a.call(fs_1);
|
||||
}
|
||||
finally { if (e_2) throw e_2.error; }
|
||||
}
|
||||
};
|
||||
for (var entity in schema) {
|
||||
if (['operEntity'].includes(entity)) {
|
||||
continue; // OperEntity会指向每一个对象,不必处理
|
||||
}
|
||||
var attributes = schema[entity].attributes;
|
||||
for (var attr in attributes) {
|
||||
if (attributes[attr].type === 'ref') {
|
||||
addToMto(entity, attributes[attr].ref, attr);
|
||||
}
|
||||
else if (attr === 'entity') {
|
||||
if (attributes[attr].ref) {
|
||||
addToMtoEntity(entity, attributes[attr].ref);
|
||||
}
|
||||
else if (process.env.NODE_ENV === 'development') {
|
||||
console.warn("".concat(entity, "\u7684entity\u53CD\u6307\u6307\u9488\u627E\u4E0D\u5230\u6709\u6548\u7684\u5BF9\u8C61"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当删除一时,要确认多上面没有指向一的数据
|
||||
var entities = (0, lodash_1.intersection)(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix));
|
||||
var _loop_3 = function (entity) {
|
||||
checkers.push({
|
||||
entity: entity,
|
||||
action: 'remove',
|
||||
type: 'logical',
|
||||
checker: function (operation, context, option) {
|
||||
var e_3, _a, e_4, _b;
|
||||
var promises = [];
|
||||
if (OneToManyMatrix[entity]) {
|
||||
var _loop_4 = function (otm) {
|
||||
var _g, _h, _j, _k;
|
||||
var _l = tslib_1.__read(otm, 2), e = _l[0], attr = _l[1];
|
||||
var proj = (_g = {
|
||||
id: 1
|
||||
},
|
||||
_g[attr] = 1,
|
||||
_g);
|
||||
var filter = operation.filter && (_h = {},
|
||||
_h[attr.slice(0, attr.length - 2)] = operation.filter,
|
||||
_h);
|
||||
var result = context.select(e, {
|
||||
data: proj,
|
||||
filter: filter,
|
||||
indexFrom: 0,
|
||||
count: 1
|
||||
}, { dontCollect: true });
|
||||
if (result instanceof Promise) {
|
||||
promises.push(result.then(function (_a) {
|
||||
var _b, _c;
|
||||
var _d = tslib_1.__read(_a, 1), row = _d[0];
|
||||
if (row) {
|
||||
var record = {
|
||||
a: 's',
|
||||
d: (_b = {},
|
||||
_b[e] = (_c = {},
|
||||
_c[row.id] = row,
|
||||
_c),
|
||||
_b)
|
||||
};
|
||||
throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(e, "\u300D\u5173\u8054\u7684\u884C"));
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
var _m = tslib_1.__read(result, 1), row = _m[0];
|
||||
if (row) {
|
||||
var record = {
|
||||
a: 's',
|
||||
d: (_j = {},
|
||||
_j[e] = (_k = {},
|
||||
_k[row.id] = row,
|
||||
_k),
|
||||
_j)
|
||||
};
|
||||
throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(e, "\u300D\u5173\u8054\u7684\u884C"));
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
for (var _c = (e_3 = void 0, tslib_1.__values(OneToManyMatrix[entity])), _d = _c.next(); !_d.done; _d = _c.next()) {
|
||||
var otm = _d.value;
|
||||
_loop_4(otm);
|
||||
}
|
||||
}
|
||||
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
||||
}
|
||||
finally { if (e_3) throw e_3.error; }
|
||||
}
|
||||
}
|
||||
if (OneToManyOnEntityMatrix[entity]) {
|
||||
var _loop_5 = function (otm) {
|
||||
var _o, _p, _q;
|
||||
var proj = {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
};
|
||||
var filter = operation.filter && (_o = {},
|
||||
_o[entity] = operation.filter,
|
||||
_o);
|
||||
var result = context.select(otm, {
|
||||
data: proj,
|
||||
filter: filter,
|
||||
indexFrom: 0,
|
||||
count: 1
|
||||
}, { dontCollect: true });
|
||||
if (result instanceof Promise) {
|
||||
promises.push(result.then(function (_a) {
|
||||
var _b, _c;
|
||||
var _d = tslib_1.__read(_a, 1), row = _d[0];
|
||||
if (row) {
|
||||
var record = {
|
||||
a: 's',
|
||||
d: (_b = {},
|
||||
_b[otm] = (_c = {},
|
||||
_c[row.id] = row,
|
||||
_c),
|
||||
_b)
|
||||
};
|
||||
throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(otm, "\u300D\u5173\u8054\u7684\u884C"));
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
var _r = tslib_1.__read(result, 1), row = _r[0];
|
||||
if (row) {
|
||||
var record = {
|
||||
a: 's',
|
||||
d: (_p = {},
|
||||
_p[otm] = (_q = {},
|
||||
_q[row.id] = row,
|
||||
_q),
|
||||
_p)
|
||||
};
|
||||
throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(otm, "\u300D\u5173\u8054\u7684\u884C"));
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
for (var _e = (e_4 = void 0, tslib_1.__values(OneToManyOnEntityMatrix[entity])), _f = _e.next(); !_f.done; _f = _e.next()) {
|
||||
var otm = _f.value;
|
||||
_loop_5(otm);
|
||||
}
|
||||
}
|
||||
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
|
||||
}
|
||||
finally { if (e_4) throw e_4.error; }
|
||||
}
|
||||
}
|
||||
if (promises.length > 0) {
|
||||
return Promise.all(promises).then(function () { return undefined; });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
try {
|
||||
for (var entities_1 = tslib_1.__values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) {
|
||||
var entity = entities_1_1.value;
|
||||
_loop_3(entity);
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) _a.call(entities_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
return checkers;
|
||||
}
|
||||
exports.createRemoveCheckers = createRemoveCheckers;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export declare type DataChecker<ED extends EntityDict, T extends keyof ED, Cxt e
|
|||
type: 'data';
|
||||
entity: T;
|
||||
action: Omit<ED[T]['Action'], 'remove'> | Array<Omit<ED[T]['Action'], 'remove'>>;
|
||||
checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => void | Promise<void>;
|
||||
checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => any | Promise<any>;
|
||||
conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise<ED[T]['Selection']['filter']>);
|
||||
};
|
||||
export declare type RowChecker<ED extends EntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = {
|
||||
|
|
@ -44,7 +44,7 @@ export declare type LogicalChecker<ED extends EntityDict, T extends keyof ED, Cx
|
|||
when?: 'after';
|
||||
entity: T;
|
||||
action: ED[T]['Action'] | Array<ED[T]['Action']>;
|
||||
checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => void | Promise<void>;
|
||||
checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => any | Promise<any>;
|
||||
conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']);
|
||||
};
|
||||
export declare type LogicalRelationChecker<ED extends EntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = {
|
||||
|
|
@ -53,7 +53,7 @@ export declare type LogicalRelationChecker<ED extends EntityDict, T extends keyo
|
|||
when?: 'after';
|
||||
entity: T;
|
||||
action: ED[T]['Action'] | Array<ED[T]['Action']>;
|
||||
checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => void | Promise<void>;
|
||||
checker: (operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt, option: OperateOption | SelectOption) => any | Promise<any>;
|
||||
conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']);
|
||||
};
|
||||
export declare type Checker<ED extends EntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = DataChecker<ED, T, Cxt> | RowChecker<ED, T, Cxt> | RelationChecker<ED, T, Cxt> | LogicalChecker<ED, T, Cxt> | LogicalRelationChecker<ED, T, Cxt>;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export interface Index<SH extends EntityShape> {
|
|||
export interface Attribute {
|
||||
type: DataType | Ref;
|
||||
params?: DataTypeParams;
|
||||
ref?: string;
|
||||
ref?: string | string[];
|
||||
onRefDelete?: 'delete' | 'setNull' | 'ignore';
|
||||
default?: string | number | boolean;
|
||||
notNull?: boolean;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ import { AsyncContext } from "../store/AsyncRowStore";
|
|||
import { SyncContext } from "../store/SyncRowStore";
|
||||
import { EntityDict, OperateOption } from "../types/Entity";
|
||||
import { EntityShape } from "../types/Entity";
|
||||
/**
|
||||
* 优先级越小,越早执行。定义在1~99之间
|
||||
*/
|
||||
export declare const TRIGGER_DEFAULT_PRIORITY = 50;
|
||||
export declare const TRIGGER_MIN_PRIORITY = 1;
|
||||
export declare const TRIGGER_MAX_PRIORITY = 99;
|
||||
export declare const DATA_CHECKER_DEFAULT_PRIORITY = 60;
|
||||
export declare const CHECKER_DEFAULT_PRIORITY = 99;
|
||||
interface TriggerBase<ED extends EntityDict, T extends keyof ED> {
|
||||
checkerType?: CheckerType;
|
||||
entity: T;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CHECKER_DEFAULT_PRIORITY = exports.DATA_CHECKER_DEFAULT_PRIORITY = exports.TRIGGER_MAX_PRIORITY = exports.TRIGGER_MIN_PRIORITY = exports.TRIGGER_DEFAULT_PRIORITY = void 0;
|
||||
/**
|
||||
* 优先级越小,越早执行。定义在1~99之间
|
||||
*/
|
||||
exports.TRIGGER_DEFAULT_PRIORITY = 50;
|
||||
exports.TRIGGER_MIN_PRIORITY = 1;
|
||||
exports.TRIGGER_MAX_PRIORITY = 99;
|
||||
exports.DATA_CHECKER_DEFAULT_PRIORITY = 60;
|
||||
exports.CHECKER_DEFAULT_PRIORITY = 99;
|
||||
;
|
||||
;
|
||||
;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "oak-domain",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.4",
|
||||
"author": {
|
||||
"name": "XuChang"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { EntityDict } from '../base-app-domain';
|
||||
import { AsyncContext } from '../store/AsyncRowStore';
|
||||
import { createAuthCheckers } from '../store/checker';
|
||||
import { createAuthCheckers, createRemoveCheckers } from '../store/checker';
|
||||
import { createModiRelatedCheckers } from '../store/modi';
|
||||
import { SyncContext } from '../store/SyncRowStore';
|
||||
import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDef, AuthDefDict } from '../types';
|
||||
|
|
@ -8,6 +8,7 @@ import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDef, AuthDefD
|
|||
export function createDynamicCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>, authDict?: AuthDefDict<ED>){
|
||||
const checkers: Checker<ED, keyof ED, Cxt>[] = [];
|
||||
checkers.push(...createModiRelatedCheckers<ED, Cxt>(schema));
|
||||
// checkers.push(...createRemoveCheckers<ED, Cxt>(schema));
|
||||
if (authDict) {
|
||||
checkers.push(...createAuthCheckers<ED, Cxt>(schema, authDict));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5222,6 +5222,26 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] {
|
|||
)
|
||||
)
|
||||
);
|
||||
// 如果是entity,在这里处理一下ref
|
||||
if (ts.isIdentifier(name) && name.text === 'entity') {
|
||||
const mtoRelations = ReversePointerRelations[entity];
|
||||
if (mtoRelations) {
|
||||
const mtoEntities = mtoRelations.map(
|
||||
ele => firstLetterLowerCase(ele)
|
||||
);
|
||||
attrAssignments.push(
|
||||
factory.createPropertyAssignment(
|
||||
factory.createIdentifier("ref"),
|
||||
factory.createArrayLiteralExpression(
|
||||
mtoEntities.map(
|
||||
ele => factory.createStringLiteral(ele)
|
||||
),
|
||||
false
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Text':
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { EntityDict, OperateOption, SelectOption, TriggerDataAttribute, TriggerT
|
|||
import { EntityDict as BaseEntityDict } from '../base-app-domain';
|
||||
import { Logger } from "../types/Logger";
|
||||
import { Checker, CheckerType, LogicalChecker, RelationChecker } from '../types/Auth';
|
||||
import { Trigger, CreateTriggerCrossTxn, CreateTrigger, CreateTriggerInTxn, SelectTriggerAfter, UpdateTrigger } from "../types/Trigger";
|
||||
import { Trigger, CreateTriggerCrossTxn, CreateTrigger, CreateTriggerInTxn, SelectTriggerAfter, UpdateTrigger, TRIGGER_DEFAULT_PRIORITY, CHECKER_DEFAULT_PRIORITY, DATA_CHECKER_DEFAULT_PRIORITY, TRIGGER_MAX_PRIORITY, TRIGGER_MIN_PRIORITY } from "../types/Trigger";
|
||||
import { AsyncContext } from './AsyncRowStore';
|
||||
import { SyncContext } from './SyncRowStore';
|
||||
import { translateCheckerInAsyncContext } from './checker';
|
||||
|
|
@ -50,10 +50,11 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict> {
|
|||
const { entity, action, type, conditionalFilter } = checker;
|
||||
const triggerName = `${String(entity)}${action}权限检查-${this.counter++}`;
|
||||
const { fn, when } = translateCheckerInAsyncContext(checker);
|
||||
const priority = type === 'data' ? DATA_CHECKER_DEFAULT_PRIORITY : CHECKER_DEFAULT_PRIORITY; // checker的默认优先级最低(前面的trigger可能会赋上一些相应的值)
|
||||
const trigger = {
|
||||
checkerType: type,
|
||||
name: triggerName,
|
||||
priority: checker.priority || 20, // checker的默认优先级稍高
|
||||
priority: checker.priority || priority,
|
||||
entity,
|
||||
action: action as 'update',
|
||||
fn,
|
||||
|
|
@ -77,7 +78,10 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict> {
|
|||
throw new Error(`不可有同名的触发器「${trigger.name}」`);
|
||||
}
|
||||
if (typeof trigger.priority !== 'number') {
|
||||
trigger.priority = 10; // 默认值
|
||||
trigger.priority = TRIGGER_DEFAULT_PRIORITY; // 默认值
|
||||
}
|
||||
else {
|
||||
assert(trigger.priority <= TRIGGER_MAX_PRIORITY && trigger.priority >= TRIGGER_MIN_PRIORITY, `trigger「${trigger.name}」的优先级定义越界,应该在${TRIGGER_MIN_PRIORITY}到${TRIGGER_MAX_PRIORITY}之间`);
|
||||
}
|
||||
if ((trigger as UpdateTrigger<ED, T, Cxt>).filter) {
|
||||
assert(typeof trigger.action === 'string' && trigger.action !== 'create'
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import { addFilterSegment, checkFilterContains, combineFilters } from "../store/
|
|||
import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception';
|
||||
import {
|
||||
AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn,
|
||||
EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy
|
||||
EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult
|
||||
} from "../types";
|
||||
import { EntityDict as BaseEntityDict } from '../base-app-domain';
|
||||
import { AsyncContext } from "./AsyncRowStore";
|
||||
import { getFullProjection } from './actionDef';
|
||||
import { SyncContext } from './SyncRowStore';
|
||||
import { firstLetterUpperCase } from '../utils/string';
|
||||
import { uniq, difference } from '../utils/lodash';
|
||||
import { intersection, uniq, difference } from '../utils/lodash';
|
||||
import { judgeRelation } from './relation';
|
||||
|
||||
export function translateCheckerInAsyncContext<
|
||||
|
|
@ -111,7 +111,7 @@ export function translateCheckerInAsyncContext<
|
|||
const filter2 = await relationFilter(operation, context, option);
|
||||
|
||||
const { data } = operation as ED[keyof ED]['Create'];
|
||||
const filter = data instanceof Array ? {
|
||||
const filter = data instanceof Array ? {
|
||||
id: {
|
||||
$in: data.map(
|
||||
ele => ele.id,
|
||||
|
|
@ -371,7 +371,12 @@ function translateActionAuthFilterMaker<ED extends EntityDict & BaseEntityDict>(
|
|||
return (userId) => filterMaker(userId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据权限定义,创建出相应的checker
|
||||
* @param schema
|
||||
* @param authDict
|
||||
* @returns
|
||||
*/
|
||||
export function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(
|
||||
schema: StorageSchema<ED>,
|
||||
authDict: AuthDefDict<ED>) {
|
||||
|
|
@ -478,3 +483,179 @@ export function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt e
|
|||
|
||||
return checkers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现
|
||||
* @param schema
|
||||
* @returns
|
||||
* 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功
|
||||
*/
|
||||
export function createRemoveCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>) {
|
||||
const checkers: Checker<ED, keyof ED, Cxt>[] = [];
|
||||
|
||||
// 先建立所有的一对多的关系
|
||||
const OneToManyMatrix: Partial<Record<keyof ED, Array<[keyof ED, string]>>> = {};
|
||||
const OneToManyOnEntityMatrix: Partial<Record<keyof ED, Array<keyof ED>>> = {};
|
||||
|
||||
const addToMto = (e: keyof ED, f: keyof ED, attr: string) => {
|
||||
if (OneToManyMatrix[f]) {
|
||||
OneToManyMatrix[f]?.push([e, attr]);
|
||||
}
|
||||
else {
|
||||
OneToManyMatrix[f] = [[e, attr]];
|
||||
}
|
||||
};
|
||||
|
||||
const addToMtoEntity = (e: keyof ED, fs: Array<keyof ED>) => {
|
||||
for (const f of fs) {
|
||||
if (!OneToManyOnEntityMatrix[f]) {
|
||||
OneToManyOnEntityMatrix[f] = [e];
|
||||
}
|
||||
else {
|
||||
OneToManyOnEntityMatrix[f]?.push(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const entity in schema) {
|
||||
if (['operEntity'].includes(entity)) {
|
||||
continue; // OperEntity会指向每一个对象,不必处理
|
||||
}
|
||||
const { attributes } = schema[entity];
|
||||
for (const attr in attributes) {
|
||||
if (attributes[attr].type === 'ref') {
|
||||
addToMto(entity, attributes[attr].ref as keyof ED, attr);
|
||||
}
|
||||
else if (attr === 'entity') {
|
||||
if (attributes[attr].ref) {
|
||||
addToMtoEntity(entity, attributes[attr].ref as Array<keyof ED>);
|
||||
}
|
||||
else if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(`${entity}的entity反指指针找不到有效的对象`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 当删除一时,要确认多上面没有指向一的数据
|
||||
const entities = intersection(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix));
|
||||
for (const entity of entities) {
|
||||
checkers.push({
|
||||
entity: entity as keyof ED,
|
||||
action: 'remove',
|
||||
type: 'logical',
|
||||
checker: (operation, context, option) => {
|
||||
const promises: Promise<void>[] = [];
|
||||
if (OneToManyMatrix[entity]) {
|
||||
for (const otm of OneToManyMatrix[entity]!) {
|
||||
const [e, attr] = otm;
|
||||
const proj = {
|
||||
id: 1,
|
||||
[attr]: 1,
|
||||
};
|
||||
const filter = operation.filter && {
|
||||
[attr.slice(0, attr.length -2)]: operation.filter
|
||||
}
|
||||
const result = context.select(e, {
|
||||
data: proj,
|
||||
filter,
|
||||
indexFrom: 0,
|
||||
count: 1
|
||||
}, { dontCollect: true });
|
||||
if (result instanceof Promise) {
|
||||
promises.push(
|
||||
result.then(
|
||||
([row]) => {
|
||||
if (row) {
|
||||
const record = {
|
||||
a: 's',
|
||||
d: {
|
||||
[e]: {
|
||||
[row.id!]: row,
|
||||
}
|
||||
}
|
||||
} as SelectOpResult<ED>;
|
||||
throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${e as string}」关联的行`);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
const [row] = result;
|
||||
if (row) {
|
||||
const record = {
|
||||
a: 's',
|
||||
d: {
|
||||
[e]: {
|
||||
[row.id!]: row,
|
||||
}
|
||||
}
|
||||
} as SelectOpResult<ED>;
|
||||
throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${e as string}」关联的行`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (OneToManyOnEntityMatrix[entity]) {
|
||||
for (const otm of OneToManyOnEntityMatrix[entity]!) {
|
||||
const proj = {
|
||||
id: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
};
|
||||
const filter = operation.filter && {
|
||||
[entity]: operation.filter
|
||||
}
|
||||
const result = context.select(otm, {
|
||||
data: proj,
|
||||
filter,
|
||||
indexFrom: 0,
|
||||
count: 1
|
||||
}, { dontCollect: true });
|
||||
if (result instanceof Promise) {
|
||||
promises.push(
|
||||
result.then(
|
||||
([row]) => {
|
||||
if (row) {
|
||||
const record = {
|
||||
a: 's',
|
||||
d: {
|
||||
[otm]: {
|
||||
[row.id!]: row,
|
||||
}
|
||||
}
|
||||
} as SelectOpResult<ED>;
|
||||
throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${otm as string}」关联的行`);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
const [row] = result;
|
||||
if (row) {
|
||||
const record = {
|
||||
a: 's',
|
||||
d: {
|
||||
[otm]: {
|
||||
[row.id!]: row,
|
||||
}
|
||||
}
|
||||
} as SelectOpResult<ED>;
|
||||
throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${otm as string}」关联的行`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (promises.length > 0) {
|
||||
return Promise.all(promises).then(
|
||||
() => undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return checkers;
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ export type DataChecker<ED extends EntityDict, T extends keyof ED, Cxt extends A
|
|||
type: 'data';
|
||||
entity: T;
|
||||
action: Omit<ED[T]['Action'], 'remove'> | Array<Omit<ED[T]['Action'], 'remove'>>;
|
||||
checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => void | Promise<void>;
|
||||
checker: (data: ED[T]['Create']['data'] | ED[T]['Update']['data'], context: Cxt) => any | Promise<any>;
|
||||
conditionalFilter?: ED[T]['Update']['filter'] | (
|
||||
(operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise<ED[T]['Selection']['filter']>
|
||||
);
|
||||
|
|
@ -62,7 +62,7 @@ export type LogicalChecker<ED extends EntityDict, T extends keyof ED, Cxt extend
|
|||
operation: ED[T]['Operation'] | ED[T]['Selection'],
|
||||
context: Cxt,
|
||||
option: OperateOption | SelectOption
|
||||
) => void | Promise<void>;
|
||||
) => any | Promise<any>;
|
||||
conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']);
|
||||
};
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export type LogicalRelationChecker<ED extends EntityDict, T extends keyof ED, Cx
|
|||
operation: ED[T]['Operation'] | ED[T]['Selection'],
|
||||
context: Cxt,
|
||||
option: OperateOption | SelectOption
|
||||
) => void | Promise<void>;
|
||||
) => any | Promise<any>;
|
||||
conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export interface Index<SH extends EntityShape> {
|
|||
export interface Attribute {
|
||||
type: DataType | Ref;
|
||||
params?: DataTypeParams;
|
||||
ref?: string;
|
||||
ref?: string | string[];
|
||||
onRefDelete?: 'delete' | 'setNull' | 'ignore';
|
||||
default?: string | number | boolean;
|
||||
notNull?: boolean;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@ import { SyncContext } from "../store/SyncRowStore";
|
|||
import { EntityDict, OperateOption } from "../types/Entity";
|
||||
import { EntityShape } from "../types/Entity";
|
||||
|
||||
/**
|
||||
* 优先级越小,越早执行。定义在1~99之间
|
||||
*/
|
||||
export const TRIGGER_DEFAULT_PRIORITY = 50;
|
||||
export const TRIGGER_MIN_PRIORITY = 1;
|
||||
export const TRIGGER_MAX_PRIORITY = 99;
|
||||
export const DATA_CHECKER_DEFAULT_PRIORITY = 60;
|
||||
export const CHECKER_DEFAULT_PRIORITY = 99;
|
||||
|
||||
interface TriggerBase<ED extends EntityDict, T extends keyof ED> {
|
||||
checkerType?: CheckerType;
|
||||
entity: T;
|
||||
|
|
|
|||
Loading…
Reference in New Issue