diff --git a/lib/actions/relation.d.ts b/lib/actions/relation.d.ts index d012b77..a53fb29 100644 --- a/lib/actions/relation.d.ts +++ b/lib/actions/relation.d.ts @@ -1,5 +1,5 @@ -import { CascadeRelationItem, RelationHierarchy } from "../types/Entity"; +import { CascadeRelationItem, RelationHierarchy, EntityDict } from "../types/Entity"; export declare type GenericRelation = 'owner'; -export declare function convertHierarchyToAuth(hierarchy: RelationHierarchy): { - [K in R]?: CascadeRelationItem; +export declare function convertHierarchyToAuth(entity: T, hierarchy: RelationHierarchy>): { + [K in NonNullable]?: CascadeRelationItem; }; diff --git a/lib/actions/relation.js b/lib/actions/relation.js index 174f3fe..21e1c55 100644 --- a/lib/actions/relation.js +++ b/lib/actions/relation.js @@ -2,7 +2,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.convertHierarchyToAuth = void 0; var tslib_1 = require("tslib"); -function convertHierarchyToAuth(hierarchy) { +function convertHierarchyToAuth(entity, hierarchy) { var e_1, _a; var _b; var reverseHierarchy = {}; diff --git a/lib/checkers/index.js b/lib/checkers/index.js index 497eee8..4ef4b19 100644 --- a/lib/checkers/index.js +++ b/lib/checkers/index.js @@ -7,7 +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.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createRemoveCheckers)(schema)), false)); + checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createRemoveCheckers)(schema, authDict)), false)); if (authDict) { checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createAuthCheckers)(schema, authDict)), false)); } diff --git a/lib/store/CascadeStore.js b/lib/store/CascadeStore.js index 5f923df..dac2f0f 100644 --- a/lib/store/CascadeStore.js +++ b/lib/store/CascadeStore.js @@ -100,20 +100,27 @@ var CascadeStore = /** @class */ (function (_super) { } }); }; - var entityIds = (0, lodash_1.uniq)(result.filter(function (ele) { return ele.entity === attr; }).map(function (ele) { return ele.entityId; })); - var subRows = cascadeSelectFn.call(_this, attr, { - data: projection2[attr], - filter: { - id: { - $in: entityIds + var entityIds = (0, lodash_1.uniq)(result.filter(function (ele) { return ele.entity === attr; }).map(function (ele) { + (0, assert_1.default)(ele.entityId !== null); + return ele.entityId; + })); + if (entityIds.length > 0) { + var subRows = cascadeSelectFn.call(_this, attr, { + data: projection2[attr], + filter: { + id: { + $in: entityIds + }, }, - }, - }, context, option); - if (subRows instanceof Promise) { - return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + }, context, option); + if (subRows instanceof Promise) { + return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + } + else { + dealWithSubRows(subRows); + } } else { - dealWithSubRows(subRows); } }); } @@ -191,18 +198,20 @@ var CascadeStore = /** @class */ (function (_super) { }); }; var ids = (0, lodash_1.uniq)(result.filter(function (ele) { return !!(ele["".concat(attr, "Id")]); }).map(function (ele) { return ele["".concat(attr, "Id")]; })); - var subRows = cascadeSelectFn.call(_this, relation, { - data: projection2[attr], - filter: { - id: { - $in: ids + if (ids.length > 0) { + var subRows = cascadeSelectFn.call(_this, relation, { + data: projection2[attr], + filter: { + id: { + $in: ids + }, }, - }, - }, context, option); - if (subRows instanceof Promise) { - return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + }, context, option); + if (subRows instanceof Promise) { + return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + } + dealWithSubRows(subRows); } - dealWithSubRows(subRows); }); } } @@ -261,21 +270,23 @@ var CascadeStore = /** @class */ (function (_super) { _a)); }); }; - var subRows = cascadeSelectFn.call(_this, entity2_1, { - data: subProjection_1, - filter: (0, filter_1.combineFilters)([(_a = {}, - _a[foreignKey_1] = { - $in: ids, - }, - _a), subFilter_1]), - sorter: subSorter_1, - indexFrom: indexFrom_1, - count: count_1 - }, context, option); - if (subRows instanceof Promise) { - return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + if (ids.length > 0) { + var subRows = cascadeSelectFn.call(_this, entity2_1, { + data: subProjection_1, + filter: (0, filter_1.combineFilters)([(_a = {}, + _a[foreignKey_1] = { + $in: ids, + }, + _a), subFilter_1]), + sorter: subSorter_1, + indexFrom: indexFrom_1, + count: count_1 + }, context, option); + if (subRows instanceof Promise) { + return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + } + dealWithSubRows(subRows); } - dealWithSubRows(subRows); }); } } @@ -329,22 +340,24 @@ var CascadeStore = /** @class */ (function (_super) { _a)); }); }; - var subRows = cascadeSelectFn.call(_this, entity2_1, { - data: subProjection_1, - filter: (0, filter_1.combineFilters)([{ - entity: entity, - entityId: { - $in: ids, - } - }, subFilter_1]), - sorter: subSorter_1, - indexFrom: indexFrom_1, - count: count_1 - }, context, option); - if (subRows instanceof Promise) { - return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + if (ids.length > 0) { + var subRows = cascadeSelectFn.call(_this, entity2_1, { + data: subProjection_1, + filter: (0, filter_1.combineFilters)([{ + entity: entity, + entityId: { + $in: ids, + } + }, subFilter_1]), + sorter: subSorter_1, + indexFrom: indexFrom_1, + count: count_1 + }, context, option); + if (subRows instanceof Promise) { + return subRows.then(function (subRowss) { return dealWithSubRows(subRowss); }); + } + dealWithSubRows(subRows); } - dealWithSubRows(subRows); }); } } @@ -708,11 +721,11 @@ var CascadeStore = /** @class */ (function (_super) { */ CascadeStore.prototype.doUpdateSingleRowAsync = function (entity, operation, context, option) { return tslib_1.__awaiter(this, void 0, void 0, function () { - var data, action, operId, filter, now, _a, modiCreate, result_1, createInner, multipleCreate, data_1, data_1_1, d, createSingleOper, e_2_1, operatorId, createOper, _b, ids_1, selection, rows, modiUpsert, upsertModis, _c, originData, originId, createOper, updateAttrCount, result; - var e_2, _d, _e, _f, _g, _h, _j, _k, _l, _m; + var data, action, operId, filter, now, _a, modiCreate, result_1, createInner, multipleCreate, data_1, data_1_1, d, createSingleOper, e_2_1, operatorId, createOper, _b, ids_1, selection, rows, modiUpsert, upsertModis, _c, originData, originId, _d, createOper, updateAttrCount, result; + var e_2, _e, _f, _g, _h, _j, _k, _l; var _this = this; - return tslib_1.__generator(this, function (_o) { - switch (_o.label) { + return tslib_1.__generator(this, function (_m) { + switch (_m.label) { case 0: data = operation.data, action = operation.action, operId = operation.id, filter = operation.filter; now = Date.now(); @@ -746,7 +759,7 @@ var CascadeStore = /** @class */ (function (_super) { }; return [4 /*yield*/, this.cascadeUpdateAsync('modi', modiCreate, context, option)]; case 2: - _o.sent(); + _m.sent(); return [2 /*return*/, 1]; case 3: result_1 = 0; @@ -836,12 +849,12 @@ var CascadeStore = /** @class */ (function (_super) { if (!multipleCreate) return [3 /*break*/, 5]; return [4 /*yield*/, createInner(operation)]; case 4: - _o.sent(); + _m.sent(); return [3 /*break*/, 12]; case 5: - _o.trys.push([5, 10, 11, 12]); + _m.trys.push([5, 10, 11, 12]); data_1 = tslib_1.__values(data), data_1_1 = data_1.next(); - _o.label = 6; + _m.label = 6; case 6: if (!!data_1_1.done) return [3 /*break*/, 9]; d = data_1_1.value; @@ -852,27 +865,27 @@ var CascadeStore = /** @class */ (function (_super) { }; return [4 /*yield*/, createInner(createSingleOper)]; case 7: - _o.sent(); - _o.label = 8; + _m.sent(); + _m.label = 8; case 8: data_1_1 = data_1.next(); return [3 /*break*/, 6]; case 9: return [3 /*break*/, 12]; case 10: - e_2_1 = _o.sent(); + e_2_1 = _m.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 12]; case 11: try { - if (data_1_1 && !data_1_1.done && (_d = data_1.return)) _d.call(data_1); + if (data_1_1 && !data_1_1.done && (_e = data_1.return)) _e.call(data_1); } finally { if (e_2) throw e_2.error; } return [7 /*endfinally*/]; case 12: return [3 /*break*/, 15]; case 13: return [4 /*yield*/, createInner(operation)]; case 14: - _o.sent(); - _o.label = 15; + _m.sent(); + _m.label = 15; case 15: if (!option.dontCollect) { context.opRecords.push({ @@ -886,20 +899,20 @@ var CascadeStore = /** @class */ (function (_super) { (0, assert_1.default)(operId); return [4 /*yield*/, context.getCurrentUserId(true)]; case 16: - operatorId = _o.sent(); + operatorId = _m.sent(); if (!operatorId) return [3 /*break*/, 22]; - _e = { + _f = { id: 'dummy', action: 'create' }; - _f = { + _g = { id: operId, action: action, data: data, operatorId: operatorId }; if (!(data instanceof Array)) return [3 /*break*/, 18]; - _g = { + _h = { id: 'dummy', action: 'create' }; @@ -918,34 +931,34 @@ var CascadeStore = /** @class */ (function (_super) { }); }); }))]; case 17: - _b = (_g.data = _o.sent(), - _g); + _b = (_h.data = _m.sent(), + _h); return [3 /*break*/, 20]; case 18: - _h = { + _j = { id: 'dummy', action: 'create' }; - _j = {}; + _k = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 19: - _b = [(_h.data = (_j.id = _o.sent(), - _j.entity = entity, - _j.entityId = data.id, - _j), - _h)]; - _o.label = 20; + _b = [(_j.data = (_k.id = _m.sent(), + _k.entity = entity, + _k.entityId = data.id, + _k), + _j)]; + _m.label = 20; case 20: - createOper = (_e.data = (_f.operEntity$oper = _b, - _f), - _e); + createOper = (_f.data = (_g.operEntity$oper = _b, + _g), + _f); return [4 /*yield*/, this.cascadeUpdateAsync('oper', createOper, context, { dontCollect: true, dontCreateOper: true, })]; case 21: - _o.sent(); - _o.label = 22; + _m.sent(); + _m.label = 22; case 22: return [2 /*return*/, result_1]; case 23: ids_1 = (0, filter_2.getRelevantIds)(filter); @@ -962,9 +975,9 @@ var CascadeStore = /** @class */ (function (_super) { dontCollect: true, })]; case 24: - rows = _o.sent(); + rows = _m.sent(); ids_1.push.apply(ids_1, tslib_1.__spreadArray([], tslib_1.__read((rows.map(function (ele) { return ele.id; }))), false)); - _o.label = 25; + _m.label = 25; case 25: if (data) { this.preProcessDataUpdated(data); @@ -1003,7 +1016,7 @@ var CascadeStore = /** @class */ (function (_super) { count: 1, }, context, option)]; case 26: - upsertModis = _o.sent(); + upsertModis = _m.sent(); if (upsertModis.length > 0) { _c = upsertModis[0], originData = _c.data, originId = _c.id; modiUpsert = { @@ -1017,24 +1030,26 @@ var CascadeStore = /** @class */ (function (_super) { } }; } - _o.label = 27; + _m.label = 27; case 27: if (!!modiUpsert) return [3 /*break*/, 29]; - _k = { + modiUpsert = { id: 'dummy', - action: 'create' + action: 'create', + data: { + id: operId, + targetEntity: entity, + entity: option.modiParentEntity, + entityId: option.modiParentId, + action: action, + data: data, + iState: 'active', + filter: filter, + }, }; + if (!(ids_1.length > 0)) return [3 /*break*/, 29]; + _d = modiUpsert.data; _l = { - id: operId, - targetEntity: entity, - entity: option.modiParentEntity, - entityId: option.modiParentId, - action: action, - data: data, - iState: 'active', - filter: filter - }; - _m = { id: 'dummy', action: 'create' }; @@ -1053,14 +1068,12 @@ var CascadeStore = /** @class */ (function (_super) { }); }); }))]; case 28: - modiUpsert = (_k.data = (_l.modiEntity$modi = (_m.data = _o.sent(), - _m), - _l), - _k); - _o.label = 29; + _d.modiEntity$modi = (_l.data = _m.sent(), + _l); + _m.label = 29; case 29: return [4 /*yield*/, this.cascadeUpdateAsync('modi', modiUpsert, context, option)]; case 30: - _o.sent(); + _m.sent(); return [2 /*return*/, 1]; case 31: createOper = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { @@ -1155,15 +1168,15 @@ var CascadeStore = /** @class */ (function (_super) { return [4 /*yield*/, createOper()]; case 34: // 如果不是update动作而是用户自定义的动作,这里还是要记录oper - _o.sent(); + _m.sent(); return [2 /*return*/, 0]; case 35: return [2 /*return*/, 0]; case 36: return [4 /*yield*/, this.updateAbjointRowAsync(entity, operation, context, option)]; case 37: - result = _o.sent(); + result = _m.sent(); return [4 /*yield*/, createOper()]; case 38: - _o.sent(); + _m.sent(); return [2 /*return*/, result]; } }); diff --git a/lib/store/checker.d.ts b/lib/store/checker.d.ts index 7d95350..0b1edae 100644 --- a/lib/store/checker.d.ts +++ b/lib/store/checker.d.ts @@ -23,4 +23,4 @@ export declare function createAuthCheckers | SyncContext>(schema: StorageSchema): Checker[]; +export declare function createRemoveCheckers | SyncContext>(schema: StorageSchema, authDict?: AuthDefDict): Checker[]; diff --git a/lib/store/checker.js b/lib/store/checker.js index a7e1a33..23916de 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -5,10 +5,12 @@ var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var filter_1 = require("../store/filter"); var Exception_1 = require("../types/Exception"); +var types_1 = require("../types"); var actionDef_1 = require("./actionDef"); var string_1 = require("../utils/string"); var lodash_1 = require("../utils/lodash"); var relation_1 = require("./relation"); +var uuid_1 = require("../utils/uuid"); function translateCheckerInAsyncContext(checker) { var _this = this; var entity = checker.entity, type = checker.type, action = checker.action; @@ -406,12 +408,12 @@ function createAuthCheckers(schema, authDict) { var _b = authDict[entity], relationAuth = _b.relationAuth, actionAuth = _b.actionAuth; if (relationAuth) { var raFilterMakerDict_1 = {}; + var userEntityName_1 = "user".concat((0, string_1.firstLetterUpperCase)(entity)); for (var r in relationAuth) { Object.assign(raFilterMakerDict_1, (_a = {}, _a[r] = translateActionAuthFilterMaker(schema, relationAuth[r], entity), _a)); } - var userEntityName_1 = "user".concat((0, string_1.firstLetterUpperCase)(entity)); var entityIdAttr_1 = "".concat(entity, "Id"); checkers.push({ entity: userEntityName_1, @@ -508,7 +510,7 @@ exports.createAuthCheckers = createAuthCheckers; * @returns * 如果有的对象允许删除,需要使用trigger来处理其相关联的外键对象,这些trigger写作before,则会在checker之前执行,仍然可以删除成功 */ -function createRemoveCheckers(schema) { +function createRemoveCheckers(schema, authDict) { var e_1, _a; var checkers = []; // 先建立所有的一对多的关系 @@ -575,7 +577,7 @@ function createRemoveCheckers(schema) { var e_3, _a, e_4, _b; var promises = []; if (OneToManyMatrix[entity]) { - var _loop_4 = function (otm) { + var _loop_5 = function (otm) { var _g, _h, _j, _k; var _l = tslib_1.__read(otm, 2), e = _l[0], attr = _l[1]; var proj = (_g = { @@ -627,7 +629,7 @@ function createRemoveCheckers(schema) { 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); + _loop_5(otm); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } @@ -639,7 +641,7 @@ function createRemoveCheckers(schema) { } } if (OneToManyOnEntityMatrix[entity]) { - var _loop_5 = function (otm) { + var _loop_6 = function (otm) { var _o, _p, _q; var proj = { id: 1, @@ -690,7 +692,7 @@ function createRemoveCheckers(schema) { 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); + _loop_6(otm); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } @@ -720,6 +722,146 @@ function createRemoveCheckers(schema) { } finally { if (e_1) throw e_1.error; } } + var _loop_4 = function (entity) { + var e_5, _b; + var cascadeRemove = authDict[entity].cascadeRemove; + if (cascadeRemove) { + var entitiesOnEntityAttr = []; + var hasAllEntity = false; + var _loop_7 = function (attr) { + if (attr === '@entity') { + hasAllEntity = true; + return "continue"; + } + var rel = (0, relation_1.judgeRelation)(schema, entity, attr); + if (rel === 2) { + entitiesOnEntityAttr.push(attr); + checkers.push({ + entity: attr, + action: 'remove', + type: 'logical', + priority: types_1.REMOVE_CASCADE_PRIORITY, + checker: function (operation, context) { + var _a, _b; + var filter = operation.filter; + if (cascadeRemove[attr] === 'remove') { + return context.operate(entity, { + id: (0, uuid_1.generateNewId)(), + action: 'remove', + data: {}, + filter: filter ? (_a = {}, + _a[attr] = filter, + _a) : undefined, + }, { dontCollect: true }); + } + return context.operate(entity, { + id: (0, uuid_1.generateNewId)(), + action: 'update', + data: { + entity: null, + entityId: null, + }, + filter: filter ? (_b = {}, + _b[attr] = filter, + _b) : undefined, + }, { dontCollect: true }); + } + }); + } + else { + (0, assert_1.default)(typeof rel === 'string'); + checkers.push({ + entity: rel, + action: 'remove', + type: 'logical', + priority: types_1.REMOVE_CASCADE_PRIORITY, + checker: function (operation, context) { + var _a, _b, _c; + var filter = operation.filter; + if (cascadeRemove[attr] === 'remove') { + return context.operate(entity, { + id: (0, uuid_1.generateNewId)(), + action: 'remove', + data: {}, + filter: filter ? (_a = {}, + _a[attr] = filter, + _a) : undefined, + }, { dontCollect: true }); + } + return context.operate(entity, { + id: (0, uuid_1.generateNewId)(), + action: 'update', + data: (_b = {}, + _b["".concat(attr, "Id")] = null, + _b), + filter: filter ? (_c = {}, + _c[attr] = filter, + _c) : undefined, + }, { dontCollect: true }); + } + }); + } + }; + for (var attr in cascadeRemove) { + _loop_7(attr); + } + if (hasAllEntity) { + var attributes = schema[entity].attributes; + var ref = attributes.entity.ref; + var restEntities = (0, lodash_1.difference)(ref, entitiesOnEntityAttr); + var _loop_8 = function (e) { + checkers.push({ + entity: e, + action: 'remove', + type: 'logical', + priority: types_1.REMOVE_CASCADE_PRIORITY, + checker: function (operation, context) { + var _a, _b; + var filter = operation.filter; + if (cascadeRemove['@entity'] === 'remove') { + return context.operate(entity, { + id: (0, uuid_1.generateNewId)(), + action: 'remove', + data: {}, + filter: filter ? (_a = {}, + _a[e] = filter, + _a) : undefined, + }, { dontCollect: true }); + } + return context.operate(entity, { + id: (0, uuid_1.generateNewId)(), + action: 'update', + data: { + entity: null, + entityId: null, + }, + filter: filter ? (_b = {}, + _b[e] = filter, + _b) : undefined, + }, { dontCollect: true }); + } + }); + }; + try { + for (var restEntities_1 = (e_5 = void 0, tslib_1.__values(restEntities)), restEntities_1_1 = restEntities_1.next(); !restEntities_1_1.done; restEntities_1_1 = restEntities_1.next()) { + var e = restEntities_1_1.value; + _loop_8(e); + } + } + catch (e_5_1) { e_5 = { error: e_5_1 }; } + finally { + try { + if (restEntities_1_1 && !restEntities_1_1.done && (_b = restEntities_1.return)) _b.call(restEntities_1); + } + finally { if (e_5) throw e_5.error; } + } + } + } + }; + // 注入声明的cascade删除时的外键处理动作 + for (var entity in authDict) { + _loop_4(entity); + } return checkers; } exports.createRemoveCheckers = createRemoveCheckers; diff --git a/lib/types/Action.d.ts b/lib/types/Action.d.ts index 242e274..1dc6ee7 100644 --- a/lib/types/Action.d.ts +++ b/lib/types/Action.d.ts @@ -13,6 +13,8 @@ export declare type ActionDictOfEntityDict = { [A in keyof E[T]['OpSchema']]?: ActionDef; }; }; +export declare type CascadeActionItem = CascadeRelationItem; export declare type CascadeActionAuth = { - [K in A | GenericAction]?: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[]; + [K in A | GenericAction]?: CascadeActionItem | (CascadeActionItem | CascadeActionItem[])[]; }; +export declare type ActionOnRemove = 'setNull' | 'remove'; diff --git a/lib/types/Auth.d.ts b/lib/types/Auth.d.ts index 7c25dbe..3b62fd3 100644 --- a/lib/types/Auth.d.ts +++ b/lib/types/Auth.d.ts @@ -1,4 +1,4 @@ -import { CascadeActionAuth, CascadeRelationAuth } from "."; +import { CascadeActionAuth, CascadeRelationAuth, ActionOnRemove } from "."; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption, SelectOption } from "../types/Entity"; @@ -60,6 +60,9 @@ export declare type Checker = { relationAuth?: CascadeRelationAuth>; actionAuth?: CascadeActionAuth; + cascadeRemove?: { + [E in keyof ED[T]['OpSchema'] | '@entity']?: ActionOnRemove; + }; }; export declare type AuthDefDict = { [K in keyof ED]?: AuthDef; diff --git a/package.json b/package.json index 3c262a9..3e3179f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.5.1", + "version": "2.5.2", "author": { "name": "XuChang" }, diff --git a/src/actions/relation.ts b/src/actions/relation.ts index d958bd7..ea35e0a 100644 --- a/src/actions/relation.ts +++ b/src/actions/relation.ts @@ -1,28 +1,28 @@ -import { CascadeRelationItem, RelationHierarchy } from "../types/Entity"; +import { CascadeRelationItem, RelationHierarchy, EntityDict } from "../types/Entity"; export type GenericRelation = 'owner'; -export function convertHierarchyToAuth(hierarchy: RelationHierarchy): { - [K in R]?: CascadeRelationItem; +export function convertHierarchyToAuth(entity: T, hierarchy: RelationHierarchy>): { + [K in NonNullable]?: CascadeRelationItem; } { - const reverseHierarchy: RelationHierarchy = {}; + const reverseHierarchy: RelationHierarchy> = {}; for (const r in hierarchy) { - for (const r2 of hierarchy[r]!) { + for (const r2 of hierarchy[r as NonNullable]!) { if (reverseHierarchy[r2]) { - reverseHierarchy[r2]?.push(r); + reverseHierarchy[r2]?.push(r as NonNullable); } else { - reverseHierarchy[r2] = [r]; + reverseHierarchy[r2] = [r as NonNullable]; } } } const result: { - [K in R]?: CascadeRelationItem; + [K in NonNullable]?: CascadeRelationItem; } = {}; for (const r in reverseHierarchy) { - result[r] = { + result[r as NonNullable] = { cascadePath: '', - relations: reverseHierarchy[r], + relations: reverseHierarchy[r as NonNullable], }; } diff --git a/src/checkers/index.ts b/src/checkers/index.ts index 3ecb67b..840ce8b 100644 --- a/src/checkers/index.ts +++ b/src/checkers/index.ts @@ -8,7 +8,7 @@ import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDef, AuthDefD export function createDynamicCheckers | SyncContext>(schema: StorageSchema, authDict?: AuthDefDict){ const checkers: Checker[] = []; checkers.push(...createModiRelatedCheckers(schema)); - checkers.push(...createRemoveCheckers(schema)); + checkers.push(...createRemoveCheckers(schema, authDict)); if (authDict) { checkers.push(...createAuthCheckers(schema, authDict)); } diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index 7c5ffee..6f62d87 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -169,24 +169,32 @@ export abstract class CascadeStore exten const entityIds = uniq(result.filter( ele => ele.entity === attr ).map( - ele => ele.entityId + ele => { + assert(ele.entityId !== null); + return ele.entityId; + } ) as string[]); - const subRows = cascadeSelectFn.call(this, attr as any, { - data: projection2[attr], - filter: { - id: { - $in: entityIds - }, - } as any, - }, context, option); - if (subRows instanceof Promise) { - return subRows.then( - (subRowss) => dealWithSubRows(subRowss) - ) + if (entityIds.length > 0) { + const subRows = cascadeSelectFn.call(this, attr as any, { + data: projection2[attr], + filter: { + id: { + $in: entityIds + }, + } as any, + }, context, option); + if (subRows instanceof Promise) { + return subRows.then( + (subRowss) => dealWithSubRows(subRowss) + ) + } + else { + dealWithSubRows(subRows as any); + } } else { - dealWithSubRows(subRows as any); + } } ); @@ -286,20 +294,22 @@ export abstract class CascadeStore exten ele => ele[`${attr}Id`] ) as string[]); - const subRows = cascadeSelectFn.call(this, relation, { - data: projection2[attr], - filter: { - id: { - $in: ids - }, - } as any, - }, context, option); - if (subRows instanceof Promise) { - return subRows.then( - (subRowss) => dealWithSubRows(subRowss) - ); + if (ids.length > 0) { + const subRows = cascadeSelectFn.call(this, relation, { + data: projection2[attr], + filter: { + id: { + $in: ids + }, + } as any, + }, context, option); + if (subRows instanceof Promise) { + return subRows.then( + (subRowss) => dealWithSubRows(subRowss) + ); + } + dealWithSubRows(subRows as any); } - dealWithSubRows(subRows as any); } ); } @@ -370,23 +380,25 @@ export abstract class CascadeStore exten ); }; - const subRows = cascadeSelectFn.call(this, entity2, { - data: subProjection, - filter: combineFilters([{ - [foreignKey]: { - $in: ids, - } - }, subFilter]), - sorter: subSorter, - indexFrom, - count - }, context, option); - if (subRows instanceof Promise) { - return subRows.then( - (subRowss) => dealWithSubRows(subRowss) - ); + if (ids.length > 0) { + const subRows = cascadeSelectFn.call(this, entity2, { + data: subProjection, + filter: combineFilters([{ + [foreignKey]: { + $in: ids, + } + }, subFilter]), + sorter: subSorter, + indexFrom, + count + }, context, option); + if (subRows instanceof Promise) { + return subRows.then( + (subRowss) => dealWithSubRows(subRowss) + ); + } + dealWithSubRows(subRows as any); } - dealWithSubRows(subRows as any); } ); } @@ -452,24 +464,26 @@ export abstract class CascadeStore exten ); }; - const subRows = cascadeSelectFn.call(this, entity2, { - data: subProjection, - filter: combineFilters([{ - entity, - entityId: { - $in: ids, - } - }, subFilter]), - sorter: subSorter, - indexFrom, - count - }, context, option); - if (subRows instanceof Promise) { - return subRows.then( - (subRowss) => dealWithSubRows(subRowss) - ); + if (ids.length > 0) { + const subRows = cascadeSelectFn.call(this, entity2, { + data: subProjection, + filter: combineFilters([{ + entity, + entityId: { + $in: ids, + } + }, subFilter]), + sorter: subSorter, + indexFrom, + count + }, context, option); + if (subRows instanceof Promise) { + return subRows.then( + (subRowss) => dealWithSubRows(subRowss) + ); + } + dealWithSubRows(subRows as any); } - dealWithSubRows(subRows as any); } ); } @@ -1116,22 +1130,24 @@ export abstract class CascadeStore exten action, data, iState: 'active', - filter, - modiEntity$modi: { - id: 'dummy', - action: 'create', - data: await Promise.all( - ids.map( - async (id) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: id, - }) - ) - ), - }, + filter, }, }; + if (ids.length > 0){ + modiUpsert.data.modiEntity$modi = { + id: 'dummy', + action: 'create', + data: await Promise.all( + ids.map( + async (id) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: id, + }) + ) + ), + }; + } } await this.cascadeUpdateAsync('modi', modiUpsert!, context, option); return 1; diff --git a/src/store/checker.ts b/src/store/checker.ts index 9d9639c..2014f41 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -3,7 +3,7 @@ import { addFilterSegment, checkFilterContains, combineFilters } from "../store/ import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; import { AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn, - EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult + EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult, REMOVE_CASCADE_PRIORITY } from "../types"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; import { AsyncContext } from "./AsyncRowStore"; @@ -12,6 +12,7 @@ import { SyncContext } from './SyncRowStore'; import { firstLetterUpperCase } from '../utils/string'; import { union, uniq, difference } from '../utils/lodash'; import { judgeRelation } from './relation'; +import { generateNewId } from '../utils/uuid'; export function translateCheckerInAsyncContext< ED extends EntityDict & BaseEntityDict, @@ -387,12 +388,12 @@ export function createAuthCheckers ED[keyof ED]['Selection']['filter']>; + const userEntityName = `user${firstLetterUpperCase(entity)}`; for (const r in relationAuth) { Object.assign(raFilterMakerDict, { [r]: translateActionAuthFilterMaker(schema, relationAuth[r as NonNullable]!, entity), }); } - const userEntityName = `user${firstLetterUpperCase(entity)}`; const entityIdAttr = `${entity}Id`; checkers.push({ entity: userEntityName as keyof ED, @@ -490,7 +491,7 @@ export function createAuthCheckers | SyncContext>(schema: StorageSchema) { +export function createRemoveCheckers | SyncContext>(schema: StorageSchema, authDict?: AuthDefDict) { const checkers: Checker[] = []; // 先建立所有的一对多的关系 @@ -657,5 +658,125 @@ export function createRemoveCheckers; + let hasAllEntity = false; + for (const attr in cascadeRemove) { + if (attr === '@entity') { + hasAllEntity = true; + continue; + } + const rel = judgeRelation(schema, entity, attr); + if (rel === 2) { + entitiesOnEntityAttr.push(attr); + checkers.push({ + entity: attr, + action: 'remove', + type: 'logical', + priority: REMOVE_CASCADE_PRIORITY, // 这个checker必须在检查外键不为空的checker之前执行,否则无法完成 + checker: (operation, context) => { + const { filter } = operation; + if (cascadeRemove[attr] === 'remove') { + return context.operate(entity, { + id: generateNewId(), + action: 'remove', + data: {}, + filter: filter ? { + [attr]: filter, + }: undefined, + }, { dontCollect: true }); + } + return context.operate(entity, { + id: generateNewId(), + action: 'update', + data: { + entity: null, + entityId: null, + }, + filter: filter ? { + [attr]: filter, + }: undefined, + }, { dontCollect: true }); + + } + }); + } + else { + assert(typeof rel === 'string'); + checkers.push({ + entity: rel, + action: 'remove', + type: 'logical', + priority: REMOVE_CASCADE_PRIORITY, // 这个checker必须在检查外键不为空的checker之前执行,否则无法完成 + checker: (operation, context) => { + const { filter } = operation; + if (cascadeRemove[attr] === 'remove') { + return context.operate(entity, { + id: generateNewId(), + action: 'remove', + data: {}, + filter: filter ? { + [attr]: filter, + }: undefined, + }, { dontCollect: true }); + } + return context.operate(entity, { + id: generateNewId(), + action: 'update', + data: { + [`${attr}Id`]: null, + }, + filter: filter ? { + [attr]: filter, + }: undefined, + }, { dontCollect: true }); + } + }); + } + } + + if (hasAllEntity) { + const { attributes } = schema[entity]; + const { ref } = attributes.entity; + const restEntities = difference(ref, entitiesOnEntityAttr); + for (const e of restEntities) { + checkers.push({ + entity: e, + action: 'remove', + type: 'logical', + priority: REMOVE_CASCADE_PRIORITY, // 这个checker必须在检查外键不为空的checker之前执行,否则无法完成 + checker: (operation, context) => { + const { filter } = operation; + if (cascadeRemove['@entity'] === 'remove') { + return context.operate(entity, { + id: generateNewId(), + action: 'remove', + data: {}, + filter: filter ? { + [e]: filter, + }: undefined, + }, { dontCollect: true }); + } + return context.operate(entity, { + id: generateNewId(), + action: 'update', + data: { + entity: null, + entityId: null, + }, + filter: filter ? { + [e]: filter, + }: undefined, + }, { dontCollect: true }); + } + }); + } + } + } + } + return checkers; } \ No newline at end of file diff --git a/src/types/Action.ts b/src/types/Action.ts index daff3fe..302c3e5 100644 --- a/src/types/Action.ts +++ b/src/types/Action.ts @@ -17,7 +17,11 @@ export type ActionDictOfEntityDict = { }; }; +export type CascadeActionItem = CascadeRelationItem; + // 即在cascadePath指向的对象上,有relation关系。若relation为空则不限定关系 export type CascadeActionAuth = { - [K in A | GenericAction]?: CascadeRelationItem | (CascadeRelationItem | CascadeRelationItem[])[]; + [K in A | GenericAction]?: CascadeActionItem | (CascadeActionItem | CascadeActionItem[])[]; }; + +export type ActionOnRemove = 'setNull' | 'remove'; // 当外键指向的对象被remove时,本对象所定义的cascade动作 diff --git a/src/types/Auth.ts b/src/types/Auth.ts index db09f27..27db51f 100644 --- a/src/types/Auth.ts +++ b/src/types/Auth.ts @@ -1,4 +1,4 @@ -import { CascadeActionAuth, RelationHierarchy, CascadeRelationAuth } from "."; +import { CascadeActionAuth, RelationHierarchy, CascadeRelationAuth, ActionOnRemove } from "."; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption, SelectOption } from "../types/Entity"; @@ -88,8 +88,11 @@ export type Checker = { relationAuth?: CascadeRelationAuth>; actionAuth?: CascadeActionAuth; + cascadeRemove?: { + [E in keyof ED[T]['OpSchema'] | '@entity']?: ActionOnRemove; + } }; export type AuthDefDict = { [K in keyof ED]?: AuthDef; -}; \ No newline at end of file +};