Merge branch 'release'

This commit is contained in:
Xu Chang 2023-02-02 20:23:51 +08:00
commit c98a8c43ea
19 changed files with 511 additions and 23 deletions

View File

@ -12,7 +12,8 @@ exports.desc = {
type: "varchar", type: "varchar",
params: { params: {
length: 32 length: 32
} },
ref: ["user"]
}, },
entityId: { entityId: {
type: "varchar", type: "varchar",

View File

@ -12,7 +12,8 @@ exports.desc = {
type: "varchar", type: "varchar",
params: { params: {
length: 32 length: 32
} },
ref: ["modi", "user"]
}, },
entityId: { entityId: {
type: "varchar", type: "varchar",

View File

@ -7,6 +7,7 @@ var modi_1 = require("../store/modi");
function createDynamicCheckers(schema, authDict) { function createDynamicCheckers(schema, authDict) {
var checkers = []; var checkers = [];
checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, modi_1.createModiRelatedCheckers)(schema)), false)); checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, modi_1.createModiRelatedCheckers)(schema)), false));
// checkers.push(...createRemoveCheckers<ED, Cxt>(schema));
if (authDict) { if (authDict) {
checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createAuthCheckers)(schema, authDict)), false)); checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createAuthCheckers)(schema, authDict)), false));
} }

View File

@ -2871,6 +2871,14 @@ function constructAttributes(entity) {
attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("type"), factory.createStringLiteral("varchar")), factory.createPropertyAssignment(factory.createIdentifier("params"), factory.createObjectLiteralExpression([ 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)), factory.createPropertyAssignment(factory.createIdentifier("length"), factory.createNumericLiteral(typeArguments[0].literal.text)),
], true))); ], 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; break;
} }
case 'Text': case 'Text':

View File

@ -6,6 +6,7 @@ var assert_1 = tslib_1.__importDefault(require("assert"));
var lodash_1 = require("../utils/lodash"); var lodash_1 = require("../utils/lodash");
var filter_1 = require("../store/filter"); var filter_1 = require("../store/filter");
var Entity_1 = require("../types/Entity"); var Entity_1 = require("../types/Entity");
var Trigger_1 = require("../types/Trigger");
var SyncRowStore_1 = require("./SyncRowStore"); var SyncRowStore_1 = require("./SyncRowStore");
var checker_1 = require("./checker"); 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 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 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 _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 = { var trigger = {
checkerType: type, checkerType: type,
name: triggerName, name: triggerName,
priority: checker.priority || 20, priority: checker.priority || priority,
entity: entity, entity: entity,
action: action, action: action,
fn: fn, 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")); throw new Error("\u4E0D\u53EF\u6709\u540C\u540D\u7684\u89E6\u53D1\u5668\u300C".concat(trigger.name, "\u300D"));
} }
if (typeof trigger.priority !== 'number') { 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) { if (trigger.filter) {
(0, assert_1.default)(typeof trigger.action === 'string' && trigger.action !== 'create' (0, assert_1.default)(typeof trigger.action === 'string' && trigger.action !== 'create'

View File

@ -10,4 +10,17 @@ export declare function translateCheckerInSyncContext<ED extends EntityDict & Ba
fn: (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void; fn: (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => void;
when: 'before' | 'after'; 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>[]; 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写作beforechecker之前执行
*/
export declare function createRemoveCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(schema: StorageSchema<ED>): Checker<ED, keyof ED, Cxt>[];

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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 tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert")); var assert_1 = tslib_1.__importDefault(require("assert"));
var filter_1 = require("../store/filter"); var filter_1 = require("../store/filter");
@ -392,6 +392,12 @@ function translateActionAuthFilterMaker(schema, relationItem, entity) {
var filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity); var filterMaker = translateCascadeRelationFilterMaker(schema, relationItem, entity);
return function (userId) { return filterMaker(userId); }; return function (userId) { return filterMaker(userId); };
} }
/**
* 根据权限定义创建出相应的checker
* @param schema
* @param authDict
* @returns
*/
function createAuthCheckers(schema, authDict) { function createAuthCheckers(schema, authDict) {
var checkers = []; var checkers = [];
var _loop_1 = function (entity) { var _loop_1 = function (entity) {
@ -496,3 +502,224 @@ function createAuthCheckers(schema, authDict) {
return checkers; return checkers;
} }
exports.createAuthCheckers = createAuthCheckers; 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;

6
lib/types/Auth.d.ts vendored
View File

@ -12,7 +12,7 @@ export declare type DataChecker<ED extends EntityDict, T extends keyof ED, Cxt e
type: 'data'; type: 'data';
entity: T; entity: T;
action: Omit<ED[T]['Action'], 'remove'> | Array<Omit<ED[T]['Action'], 'remove'>>; 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']>); 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>> = { 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'; when?: 'after';
entity: T; entity: T;
action: ED[T]['Action'] | Array<ED[T]['Action']>; 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']); 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>> = { 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'; when?: 'after';
entity: T; entity: T;
action: ED[T]['Action'] | Array<ED[T]['Action']>; 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']); 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>; 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>;

View File

@ -20,7 +20,7 @@ export interface Index<SH extends EntityShape> {
export interface Attribute { export interface Attribute {
type: DataType | Ref; type: DataType | Ref;
params?: DataTypeParams; params?: DataTypeParams;
ref?: string; ref?: string | string[];
onRefDelete?: 'delete' | 'setNull' | 'ignore'; onRefDelete?: 'delete' | 'setNull' | 'ignore';
default?: string | number | boolean; default?: string | number | boolean;
notNull?: boolean; notNull?: boolean;

View File

@ -4,6 +4,14 @@ import { AsyncContext } from "../store/AsyncRowStore";
import { SyncContext } from "../store/SyncRowStore"; import { SyncContext } from "../store/SyncRowStore";
import { EntityDict, OperateOption } from "../types/Entity"; import { EntityDict, OperateOption } from "../types/Entity";
import { EntityShape } from "../types/Entity"; import { EntityShape } from "../types/Entity";
/**
* 199
*/
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> { interface TriggerBase<ED extends EntityDict, T extends keyof ED> {
checkerType?: CheckerType; checkerType?: CheckerType;
entity: T; entity: T;

View File

@ -1,5 +1,14 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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;
/**
* 优先级越小越早执行定义在199之间
*/
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;
; ;
; ;
; ;

View File

@ -1,6 +1,6 @@
{ {
"name": "oak-domain", "name": "oak-domain",
"version": "2.4.3", "version": "2.4.4",
"author": { "author": {
"name": "XuChang" "name": "XuChang"
}, },

View File

@ -1,6 +1,6 @@
import { EntityDict } from '../base-app-domain'; import { EntityDict } from '../base-app-domain';
import { AsyncContext } from '../store/AsyncRowStore'; import { AsyncContext } from '../store/AsyncRowStore';
import { createAuthCheckers } from '../store/checker'; import { createAuthCheckers, createRemoveCheckers } from '../store/checker';
import { createModiRelatedCheckers } from '../store/modi'; import { createModiRelatedCheckers } from '../store/modi';
import { SyncContext } from '../store/SyncRowStore'; import { SyncContext } from '../store/SyncRowStore';
import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDef, AuthDefDict } from '../types'; 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>){ 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>[] = []; const checkers: Checker<ED, keyof ED, Cxt>[] = [];
checkers.push(...createModiRelatedCheckers<ED, Cxt>(schema)); checkers.push(...createModiRelatedCheckers<ED, Cxt>(schema));
// checkers.push(...createRemoveCheckers<ED, Cxt>(schema));
if (authDict) { if (authDict) {
checkers.push(...createAuthCheckers<ED, Cxt>(schema, authDict)); checkers.push(...createAuthCheckers<ED, Cxt>(schema, authDict));
} }

View File

@ -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; break;
} }
case 'Text': case 'Text':

View File

@ -5,7 +5,7 @@ import { EntityDict, OperateOption, SelectOption, TriggerDataAttribute, TriggerT
import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { EntityDict as BaseEntityDict } from '../base-app-domain';
import { Logger } from "../types/Logger"; import { Logger } from "../types/Logger";
import { Checker, CheckerType, LogicalChecker, RelationChecker } from '../types/Auth'; 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 { AsyncContext } from './AsyncRowStore';
import { SyncContext } from './SyncRowStore'; import { SyncContext } from './SyncRowStore';
import { translateCheckerInAsyncContext } from './checker'; import { translateCheckerInAsyncContext } from './checker';
@ -50,10 +50,11 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict> {
const { entity, action, type, conditionalFilter } = checker; const { entity, action, type, conditionalFilter } = checker;
const triggerName = `${String(entity)}${action}权限检查-${this.counter++}`; const triggerName = `${String(entity)}${action}权限检查-${this.counter++}`;
const { fn, when } = translateCheckerInAsyncContext(checker); const { fn, when } = translateCheckerInAsyncContext(checker);
const priority = type === 'data' ? DATA_CHECKER_DEFAULT_PRIORITY : CHECKER_DEFAULT_PRIORITY; // checker的默认优先级最低前面的trigger可能会赋上一些相应的值
const trigger = { const trigger = {
checkerType: type, checkerType: type,
name: triggerName, name: triggerName,
priority: checker.priority || 20, // checker的默认优先级稍高 priority: checker.priority || priority,
entity, entity,
action: action as 'update', action: action as 'update',
fn, fn,
@ -77,7 +78,10 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict> {
throw new Error(`不可有同名的触发器「${trigger.name}`); throw new Error(`不可有同名的触发器「${trigger.name}`);
} }
if (typeof trigger.priority !== 'number') { 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) { if ((trigger as UpdateTrigger<ED, T, Cxt>).filter) {
assert(typeof trigger.action === 'string' && trigger.action !== 'create' assert(typeof trigger.action === 'string' && trigger.action !== 'create'

View File

@ -3,14 +3,14 @@ import { addFilterSegment, checkFilterContains, combineFilters } from "../store/
import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception';
import { import {
AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn, AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn,
EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult
} from "../types"; } from "../types";
import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { EntityDict as BaseEntityDict } from '../base-app-domain';
import { AsyncContext } from "./AsyncRowStore"; import { AsyncContext } from "./AsyncRowStore";
import { getFullProjection } from './actionDef'; import { getFullProjection } from './actionDef';
import { SyncContext } from './SyncRowStore'; import { SyncContext } from './SyncRowStore';
import { firstLetterUpperCase } from '../utils/string'; import { firstLetterUpperCase } from '../utils/string';
import { uniq, difference } from '../utils/lodash'; import { intersection, uniq, difference } from '../utils/lodash';
import { judgeRelation } from './relation'; import { judgeRelation } from './relation';
export function translateCheckerInAsyncContext< export function translateCheckerInAsyncContext<
@ -371,7 +371,12 @@ function translateActionAuthFilterMaker<ED extends EntityDict & BaseEntityDict>(
return (userId) => filterMaker(userId); return (userId) => filterMaker(userId);
} }
/**
* checker
* @param schema
* @param authDict
* @returns
*/
export function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>( export function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>>(
schema: StorageSchema<ED>, schema: StorageSchema<ED>,
authDict: AuthDefDict<ED>) { authDict: AuthDefDict<ED>) {
@ -478,3 +483,179 @@ export function createAuthCheckers<ED extends EntityDict & BaseEntityDict, Cxt e
return checkers; return checkers;
} }
/**
*
* @param schema
* @returns
* 使trigger来处理其相关联的外键对象trigger写作beforechecker之前执行
*/
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;
}

View File

@ -15,7 +15,7 @@ export type DataChecker<ED extends EntityDict, T extends keyof ED, Cxt extends A
type: 'data'; type: 'data';
entity: T; entity: T;
action: Omit<ED[T]['Action'], 'remove'> | Array<Omit<ED[T]['Action'], 'remove'>>; 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'] | ( conditionalFilter?: ED[T]['Update']['filter'] | (
(operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter'] | Promise<ED[T]['Selection']['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'], operation: ED[T]['Operation'] | ED[T]['Selection'],
context: Cxt, context: Cxt,
option: OperateOption | SelectOption 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']); 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'], operation: ED[T]['Operation'] | ED[T]['Selection'],
context: Cxt, context: Cxt,
option: OperateOption | SelectOption 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']); conditionalFilter?: ED[T]['Update']['filter'] | ((operation: ED[T]['Operation'], context: Cxt, option: OperateOption) => ED[T]['Update']['filter']);
}; };

View File

@ -27,7 +27,7 @@ export interface Index<SH extends EntityShape> {
export interface Attribute { export interface Attribute {
type: DataType | Ref; type: DataType | Ref;
params?: DataTypeParams; params?: DataTypeParams;
ref?: string; ref?: string | string[];
onRefDelete?: 'delete' | 'setNull' | 'ignore'; onRefDelete?: 'delete' | 'setNull' | 'ignore';
default?: string | number | boolean; default?: string | number | boolean;
notNull?: boolean; notNull?: boolean;

View File

@ -5,6 +5,15 @@ import { SyncContext } from "../store/SyncRowStore";
import { EntityDict, OperateOption } from "../types/Entity"; import { EntityDict, OperateOption } from "../types/Entity";
import { EntityShape } from "../types/Entity"; import { EntityShape } from "../types/Entity";
/**
* 199
*/
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> { interface TriggerBase<ED extends EntityDict, T extends keyof ED> {
checkerType?: CheckerType; checkerType?: CheckerType;
entity: T; entity: T;