大改了relationAuth的实现,以及cascadeStore的结构性重构

This commit is contained in:
Xu Chang 2023-07-22 19:33:53 +08:00
parent c044a7fcfe
commit 79dc2cfdbb
12 changed files with 326 additions and 130 deletions

View File

@ -4,9 +4,10 @@ export declare const ENTITY_PATH_IN_OAK_GENERAL_BUSINESS: () => string;
export declare const ENTITY_PATH_IN_OAK_DOMAIN: () => string;
export declare const TYPE_PATH_IN_OAK_DOMAIN: (level?: number) => string;
export declare const ACTION_CONSTANT_IN_OAK_DOMAIN: (level?: number) => string;
export declare const RESERVED_ENTITIES: string[];
export declare const RESERVED_ENTITY_NAMES: string[];
export declare const ENTITY_NAME_MAX_LENGTH = 32;
export declare const STRING_LITERAL_MAX_LENGTH = 24;
export declare const NUMERICAL_LITERL_DEFAULT_PRECISION = 8;
export declare const NUMERICAL_LITERL_DEFAULT_SCALE = 2;
export declare const INT_LITERL_DEFAULT_WIDTH = 4;
export declare const SYSTEM_RESERVE_ENTITIES: string[];

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.INT_LITERL_DEFAULT_WIDTH = exports.NUMERICAL_LITERL_DEFAULT_SCALE = exports.NUMERICAL_LITERL_DEFAULT_PRECISION = exports.STRING_LITERAL_MAX_LENGTH = exports.ENTITY_NAME_MAX_LENGTH = exports.RESERVED_ENTITIES = exports.ACTION_CONSTANT_IN_OAK_DOMAIN = exports.TYPE_PATH_IN_OAK_DOMAIN = exports.ENTITY_PATH_IN_OAK_DOMAIN = exports.ENTITY_PATH_IN_OAK_GENERAL_BUSINESS = exports.LIB_PATH = exports.LIB_OAK_DOMAIN = void 0;
exports.SYSTEM_RESERVE_ENTITIES = exports.INT_LITERL_DEFAULT_WIDTH = exports.NUMERICAL_LITERL_DEFAULT_SCALE = exports.NUMERICAL_LITERL_DEFAULT_PRECISION = exports.STRING_LITERAL_MAX_LENGTH = exports.ENTITY_NAME_MAX_LENGTH = exports.RESERVED_ENTITY_NAMES = exports.ACTION_CONSTANT_IN_OAK_DOMAIN = exports.TYPE_PATH_IN_OAK_DOMAIN = exports.ENTITY_PATH_IN_OAK_DOMAIN = exports.ENTITY_PATH_IN_OAK_GENERAL_BUSINESS = exports.LIB_PATH = exports.LIB_OAK_DOMAIN = void 0;
exports.LIB_OAK_DOMAIN = 'oak-domain';
var LIB_OAK_GENERAL_BUSINESS = 'oak-general-business';
var LIB_PATH = function () { return 'lib'; };
@ -31,10 +31,12 @@ var ACTION_CONSTANT_IN_OAK_DOMAIN = function (level) {
};
exports.ACTION_CONSTANT_IN_OAK_DOMAIN = ACTION_CONSTANT_IN_OAK_DOMAIN;
// export const OUTPUT_PATH = 'app-domain/entities';
exports.RESERVED_ENTITIES = ['Schema', 'Filter', 'Query', 'SubQuery', 'Entity', 'Selection', 'Operation', 'File', 'Common',
exports.RESERVED_ENTITY_NAMES = ['Schema', 'Filter', 'Query', 'SubQuery', 'Entity', 'Selection', 'Operation', 'File', 'Common',
'Locale', 'Projection', 'Data'];
exports.ENTITY_NAME_MAX_LENGTH = 32;
exports.STRING_LITERAL_MAX_LENGTH = 24;
exports.NUMERICAL_LITERL_DEFAULT_PRECISION = 8;
exports.NUMERICAL_LITERL_DEFAULT_SCALE = 2;
exports.INT_LITERL_DEFAULT_WIDTH = 4;
// 暂放在这儿
exports.SYSTEM_RESERVE_ENTITIES = ['user', 'relation', 'oper', 'operEntity', 'modi', 'modiEntity', 'userRelation', 'actionAuth', 'relationAuth', 'relation'];

View File

@ -239,7 +239,7 @@ function dealWithActions(moduleName, filename, node, program, sourceFile, hasRel
return ele.typeName.text;
}
}).filter(function (ele) { return !!ele; });
(0, assert_1.default)((0, lodash_1.intersection)(actionNames, env_1.RESERVED_ENTITIES).length === 0, "".concat(filename, "\u4E2D\u7684Action\u547D\u540D\u4E0D\u80FD\u662F\u300C").concat(RESERVED_ACTION_NAMES.join(','), "\u300D\u4E4B\u4E00"));
(0, assert_1.default)((0, lodash_1.intersection)(actionNames, RESERVED_ACTION_NAMES).length === 0, "".concat(filename, "\u4E2D\u7684Action\u547D\u540D\u4E0D\u80FD\u662F\u300C").concat(RESERVED_ACTION_NAMES.join(','), "\u300D\u4E4B\u4E00"));
node.types.forEach(function (ele) {
if (ts.isTypeReferenceNode(ele)) {
actionTexts.push.apply(actionTexts, tslib_1.__spreadArray([], tslib_1.__read(getStringTextFromUnionStringLiterals(moduleName, filename, ele, program)), false));
@ -3798,7 +3798,7 @@ function analyzeEntities(inputDir, relativePath) {
var files = (0, fs_1.readdirSync)(inputDir);
var fullFilenames = files.map(function (ele) {
var entity = ele.slice(0, ele.indexOf('.'));
if (env_1.RESERVED_ENTITIES.includes(entity) || env_1.RESERVED_ENTITIES.find(function (ele2) { return entity.startsWith(ele2); })) {
if (env_1.RESERVED_ENTITY_NAMES.includes(entity) || env_1.RESERVED_ENTITY_NAMES.find(function (ele2) { return entity.startsWith(ele2); })) {
throw new Error("".concat(ele, "\u662F\u7CFB\u7EDF\u4FDD\u7559\u5B57\uFF0C\u8BF7\u52FF\u4F7F\u7528\u5176\u5F53\u5BF9\u8C61\u540D\u6216\u5BF9\u8C61\u540D\u524D\u7F00"));
}
return "".concat(inputDir, "/").concat(ele);

View File

@ -12,9 +12,10 @@ export declare abstract class CascadeStore<ED extends EntityDict & BaseEntityDic
private selectionRewriters;
private operationRewriters;
private reinforceSelection;
private reinforceSelectionInner;
private reinforceOperation;
registerOperationRewriter(rewriter: OperationRewriter<ED>): void;
registerSelectionRewriter(rewriter: SelectionRewriter<ED>): void;
registerOperationRewriter(rewriter: OperationRewriter<ED, AsyncContext<ED>>): void;
registerSelectionRewriter(rewriter: SelectionRewriter<ED, AsyncContext<ED>>): void;
protected abstract selectAbjointRow<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial<ED[T]['Schema']>[];
protected abstract updateAbjointRow<T extends keyof ED, OP extends OperateOption, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): number;
protected abstract selectAbjointRowAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise<Partial<ED[T]['Schema']>[]>;

View File

@ -12,6 +12,7 @@ var lodash_1 = require("../utils/lodash");
var AsyncRowStore_1 = require("./AsyncRowStore");
var filter_2 = require("./filter");
var uuid_1 = require("../utils/uuid");
var env_1 = require("../compiler/env");
/**这个用来处理级联的select和update对不同能力的 */
var CascadeStore = /** @class */ (function (_super) {
tslib_1.__extends(CascadeStore, _super);
@ -21,7 +22,47 @@ var CascadeStore = /** @class */ (function (_super) {
_this.operationRewriters = [];
return _this;
}
CascadeStore.prototype.reinforceSelection = function (entity, selection) {
CascadeStore.prototype.reinforceSelection = function (entity, selection, context, option) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var noRelationDestEntities, rewriterPromises;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
noRelationDestEntities = [];
this.reinforceSelectionInner(entity, selection, context, noRelationDestEntities);
rewriterPromises = this.selectionRewriters.map(function (ele) { return ele(_this.getSchema(), entity, selection, context); });
// 这个设计每次都要取actionAuth的数据感觉不是很优雅。by Xc 20230722
if (noRelationDestEntities.length > 0 && !option.dontCollect) {
rewriterPromises.push(context.select('actionAuth', {
data: {
id: 1,
deActions: 1,
destEntity: 1,
path: 1,
relationId: 1,
},
filter: {
relationId: {
$exists: false,
},
destEntity: {
$in: noRelationDestEntities,
},
},
}, {}));
}
if (!(rewriterPromises.length > 0)) return [3 /*break*/, 2];
return [4 /*yield*/, Promise.all(rewriterPromises)];
case 1:
_a.sent();
_a.label = 2;
case 2: return [2 /*return*/];
}
});
});
};
CascadeStore.prototype.reinforceSelectionInner = function (entity, selection, context, noRelationDestEntities) {
var _this = this;
var filter = selection.filter, data = selection.data, sorter = selection.sorter;
var checkNode = function (projectionNode, attrs) {
@ -143,91 +184,131 @@ var CascadeStore = /** @class */ (function (_super) {
}
var toBeAssignNode2 = {}; // 用来记录在表达式中涉及到的结点
var projectionNodeDict = {};
var checkProjectionNode = function (entity2, projectionNode) {
var checkProjectionNode = function (entity2, projectionNode) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var necessaryAttrs, attr, exprResult, nodeName, rel, data_1, userId;
var _a, _b, _c;
var necessaryAttrs = ['id', '$$createAt$$']; // 有的页面依赖于其它页面取数据有时两个页面的filter的差异会导致有一个加createAt有一个不加此时可能产生前台取数据不完整的异常。先统一加上
for (var attr in projectionNode) {
if (attr === '#id') {
(0, assert_1.default)(!projectionNodeDict[projectionNode[attr]], "projection\u4E2D\u7ED3\u70B9\u7684id\u6709\u91CD\u590D, ".concat(projectionNode[attr]));
Object.assign(projectionNodeDict, (_a = {},
_a[projectionNode[attr]] = projectionNode,
_a));
if (toBeAssignNode2[projectionNode[attr]]) {
checkNode(projectionNode, toBeAssignNode2[projectionNode[attr]]);
}
}
else {
if (attr.toLowerCase().startsWith(types_1.EXPRESSION_PREFIX)) {
var exprResult = (0, types_1.getAttrRefInExpression)(projectionNode[attr]);
for (var nodeName in exprResult) {
if (nodeName === '#current') {
checkNode(projectionNode, exprResult[nodeName]);
}
else if (projectionNodeDict[nodeName]) {
checkNode(projectionNodeDict[nodeName], exprResult[nodeName]);
}
else {
if (toBeAssignNode2[nodeName]) {
(_b = toBeAssignNode2[nodeName]).push.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(exprResult[nodeName]), false));
}
else {
Object.assign(toBeAssignNode2, (_c = {},
_c[nodeName] = exprResult[nodeName],
_c));
}
}
return tslib_1.__generator(this, function (_d) {
necessaryAttrs = ['id', '$$createAt$$'];
for (attr in projectionNode) {
if (attr === '#id') {
(0, assert_1.default)(!projectionNodeDict[projectionNode[attr]], "projection\u4E2D\u7ED3\u70B9\u7684id\u6709\u91CD\u590D, ".concat(projectionNode[attr]));
Object.assign(projectionNodeDict, (_a = {},
_a[projectionNode[attr]] = projectionNode,
_a));
if (toBeAssignNode2[projectionNode[attr]]) {
checkNode(projectionNode, toBeAssignNode2[projectionNode[attr]]);
}
}
else {
var rel = (0, relation_1.judgeRelation)(_this.getSchema(), entity2, attr);
if (rel === 1) {
necessaryAttrs.push(attr);
}
else if (rel === 2) {
// entity/entityId反指
necessaryAttrs.push('entity', 'entityId');
checkProjectionNode(attr, projectionNode[attr]);
}
else if (typeof rel === 'string') {
necessaryAttrs.push("".concat(attr, "Id"));
checkProjectionNode(rel, projectionNode[attr]);
}
else if (rel instanceof Array && !attr.endsWith('$$aggr')) {
var data_1 = projectionNode[attr].data;
if (rel[1]) {
checkNode(data_1, [rel[1]]);
if (attr.toLowerCase().startsWith(types_1.EXPRESSION_PREFIX)) {
exprResult = (0, types_1.getAttrRefInExpression)(projectionNode[attr]);
for (nodeName in exprResult) {
if (nodeName === '#current') {
checkNode(projectionNode, exprResult[nodeName]);
}
else if (projectionNodeDict[nodeName]) {
checkNode(projectionNodeDict[nodeName], exprResult[nodeName]);
}
else {
if (toBeAssignNode2[nodeName]) {
(_b = toBeAssignNode2[nodeName]).push.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(exprResult[nodeName]), false));
}
else {
Object.assign(toBeAssignNode2, (_c = {},
_c[nodeName] = exprResult[nodeName],
_c));
}
}
}
else {
checkNode(data_1, ['entity', 'entityId']);
}
else {
rel = (0, relation_1.judgeRelation)(this.getSchema(), entity2, attr);
if (rel === 1) {
necessaryAttrs.push(attr);
}
else if (rel === 2) {
// entity/entityId反指
necessaryAttrs.push('entity', 'entityId');
checkProjectionNode(attr, projectionNode[attr]);
}
else if (typeof rel === 'string') {
necessaryAttrs.push("".concat(attr, "Id"));
checkProjectionNode(rel, projectionNode[attr]);
}
else if (rel instanceof Array && !attr.endsWith('$$aggr')) {
data_1 = projectionNode[attr].data;
if (rel[1]) {
checkNode(data_1, [rel[1]]);
}
else {
checkNode(data_1, ['entity', 'entityId']);
}
this.reinforceSelectionInner(rel[0], projectionNode[attr], context, noRelationDestEntities);
}
_this.reinforceSelection(rel[0], projectionNode[attr]);
}
}
checkNode(projectionNode, necessaryAttrs);
}
checkNode(projectionNode, necessaryAttrs);
}
// 如果对象中指向一对多的Modi此时加上指向Modi的projection
if (_this.getSchema()[entity2].toModi) {
Object.assign(projectionNode, {
modi$entity: {
$entity: 'modi',
data: {
id: 1,
targetEntity: 1,
entity: 1,
entityId: 1,
action: 1,
iState: 1,
data: 1,
filter: 1,
},
filter: {
iState: 'active',
},
// 如果对象中指向一对多的Modi此时加上指向Modi的projection
if (this.getSchema()[entity2].toModi) {
Object.assign(projectionNode, {
modi$entity: {
$entity: 'modi',
data: {
id: 1,
targetEntity: 1,
entity: 1,
entityId: 1,
action: 1,
iState: 1,
data: 1,
filter: 1,
},
filter: {
iState: 'active',
},
}
});
}
userId = context.getCurrentUserId(true);
if (userId && !env_1.SYSTEM_RESERVE_ENTITIES.includes(entity2)) {
if (this.getSchema()[entity2].relation && !projectionNode.userRelation$entity) {
Object.assign(projectionNode, {
userRelation$entity: {
$entity: 'userRelation',
data: {
id: 1,
entity: 1,
entityId: 1,
userId: 1,
relationId: 1,
relation: {
id: 1,
name: 1,
display: 1,
actionAuth$relation: {
$entity: 'actionAuth',
data: {
id: 1,
deActions: 1,
destEntity: 1,
path: 1,
relationId: 1,
},
}
}
},
filter: {
userId: userId,
},
},
});
}
});
}
};
noRelationDestEntities.push(entity2);
}
return [2 /*return*/];
});
}); };
checkProjectionNode(entity, data);
if (!sorter && relevantIds.length === 0) {
// 如果没有sorter就给予一个按createAt逆序的sorter
@ -245,11 +326,19 @@ var CascadeStore = /** @class */ (function (_super) {
$$createAt$$: 1,
});
}
this.selectionRewriters.forEach(function (ele) { return ele(_this.getSchema(), entity, selection); });
};
CascadeStore.prototype.reinforceOperation = function (entity, operation) {
var _this = this;
this.operationRewriters.forEach(function (ele) { return ele(_this.getSchema(), entity, operation); });
CascadeStore.prototype.reinforceOperation = function (entity, operation, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, Promise.all(this.operationRewriters.map(function (ele) { return ele(_this.getSchema(), entity, operation, context); }))];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
CascadeStore.prototype.registerOperationRewriter = function (rewriter) {
this.operationRewriters.push(rewriter);
@ -1651,7 +1740,6 @@ var CascadeStore = /** @class */ (function (_super) {
return tslib_1.__generator(this, function (_h) {
switch (_h.label) {
case 0:
this.reinforceOperation(entity, operation);
action = operation.action, data = operation.data, filter = operation.filter, id = operation.id;
wholeBeforeFns = [];
wholeAfterFns = [];
@ -1750,7 +1838,6 @@ var CascadeStore = /** @class */ (function (_super) {
});
};
CascadeStore.prototype.cascadeSelect = function (entity, selection, context, option) {
this.reinforceSelection(entity, selection);
var data = selection.data, filter = selection.filter, indexFrom = selection.indexFrom, count = selection.count, sorter = selection.sorter;
var _a = this.destructCascadeSelect(entity, data, context, this.cascadeSelect, this.aggregateSync, option), projection = _a.projection, cascadeSelectionFns = _a.cascadeSelectionFns;
var rows = this.selectAbjointRow(entity, {
@ -1926,8 +2013,12 @@ var CascadeStore = /** @class */ (function (_super) {
CascadeStore.prototype.selectAsync = function (entity, selection, context, option) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
this.reinforceSelection(entity, selection);
return [2 /*return*/, this.cascadeSelectAsync(entity, selection, context, option)];
switch (_a.label) {
case 0: return [4 /*yield*/, this.reinforceSelection(entity, selection, context, option)];
case 1:
_a.sent();
return [2 /*return*/, this.cascadeSelectAsync(entity, selection, context, option)];
}
});
});
};
@ -1939,8 +2030,16 @@ var CascadeStore = /** @class */ (function (_super) {
return this.cascadeUpdate(entity, operation, context, option);
};
CascadeStore.prototype.operateAsync = function (entity, operation, context, option) {
this.reinforceOperation(entity, operation);
return this.operateAsync(entity, operation, context, option);
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.reinforceOperation(entity, operation, context)];
case 1:
_a.sent();
return [2 /*return*/, this.cascadeUpdateAsync(entity, operation, context, option)];
}
});
});
};
return CascadeStore;
}(RowStore_1.RowStore));

View File

@ -10,6 +10,7 @@ var relation_1 = require("./relation");
var SyncRowStore_1 = require("./SyncRowStore");
var action_1 = require("../actions/action");
var lodash_1 = require("../utils/lodash");
var env_1 = require("../compiler/env");
var RelationAuth = /** @class */ (function () {
function RelationAuth(schema, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap, selectFreeEntities) {
this.actionCascadePathGraph = actionCascadePathGraph;
@ -1911,7 +1912,7 @@ var RelationAuth = /** @class */ (function () {
}
}
};
RelationAuth.SPECIAL_ENTITIES = ['user', 'relation', 'oper', 'operEntity', 'modi', 'modiEntity', 'userRelation', 'actionAuth', 'relationAuth', 'relation'];
RelationAuth.SPECIAL_ENTITIES = env_1.SYSTEM_RESERVE_ENTITIES;
return RelationAuth;
}());
exports.RelationAuth = RelationAuth;

View File

@ -1,15 +1,16 @@
import { OperationResult, EntityDict } from './Entity';
import { StorageSchema } from './Storage';
import { AsyncContext } from '../store/AsyncRowStore';
export declare type TxnOption = {
isolationLevel: 'repeatable read' | 'serializable';
};
export declare type SelectionRewriter<ED extends EntityDict> = (schema: StorageSchema<ED>, entity: keyof ED, selection: ED[keyof ED]['Selection']) => void;
export declare type OperationRewriter<ED extends EntityDict> = (schema: StorageSchema<ED>, entity: keyof ED, operate: ED[keyof ED]['Operation']) => void;
export declare type SelectionRewriter<ED extends EntityDict, Cxt extends AsyncContext<ED>> = (schema: StorageSchema<ED>, entity: keyof ED, selection: ED[keyof ED]['Selection'], context: Cxt) => Promise<void>;
export declare type OperationRewriter<ED extends EntityDict, Cxt extends AsyncContext<ED>> = (schema: StorageSchema<ED>, entity: keyof ED, operate: ED[keyof ED]['Operation'], context: Cxt) => Promise<void>;
export declare abstract class RowStore<ED extends EntityDict> {
protected storageSchema: StorageSchema<ED>;
constructor(storageSchema: StorageSchema<ED>);
abstract registerOperationRewriter(rewriter: OperationRewriter<ED>): void;
abstract registerSelectionRewriter(rewriter: SelectionRewriter<ED>): void;
abstract registerOperationRewriter(rewriter: OperationRewriter<ED, AsyncContext<ED>>): void;
abstract registerSelectionRewriter(rewriter: SelectionRewriter<ED, AsyncContext<ED>>): void;
getSchema(): StorageSchema<ED>;
mergeOperationResult(result: OperationResult<ED>, toBeMerged: OperationResult<ED>): void;
mergeMultipleResults(toBeMerged: OperationResult<ED>[]): OperationResult<ED>;

View File

@ -27,10 +27,13 @@ export const ACTION_CONSTANT_IN_OAK_DOMAIN = (level = 2) => {
// export const OUTPUT_PATH = 'app-domain/entities';
export const RESERVED_ENTITIES = ['Schema', 'Filter', 'Query', 'SubQuery', 'Entity', 'Selection', 'Operation', 'File', 'Common',
export const RESERVED_ENTITY_NAMES = ['Schema', 'Filter', 'Query', 'SubQuery', 'Entity', 'Selection', 'Operation', 'File', 'Common',
'Locale', 'Projection', 'Data'];
export const ENTITY_NAME_MAX_LENGTH = 32;
export const STRING_LITERAL_MAX_LENGTH = 24;
export const NUMERICAL_LITERL_DEFAULT_PRECISION = 8;
export const NUMERICAL_LITERL_DEFAULT_SCALE = 2;
export const INT_LITERL_DEFAULT_WIDTH = 4;
export const INT_LITERL_DEFAULT_WIDTH = 4;
// 暂放在这儿
export const SYSTEM_RESERVE_ENTITIES = ['user', 'relation', 'oper', 'operEntity', 'modi', 'modiEntity', 'userRelation', 'actionAuth', 'relationAuth', 'relation'];

View File

@ -8,7 +8,7 @@ const { factory } = ts;
import {
ACTION_CONSTANT_IN_OAK_DOMAIN,
TYPE_PATH_IN_OAK_DOMAIN,
RESERVED_ENTITIES,
RESERVED_ENTITY_NAMES,
STRING_LITERAL_MAX_LENGTH,
NUMERICAL_LITERL_DEFAULT_PRECISION,
NUMERICAL_LITERL_DEFAULT_SCALE,
@ -362,7 +362,7 @@ function dealWithActions(moduleName: string, filename: string, node: ts.TypeNode
).filter(
ele => !!ele
);
assert(intersection(actionNames, RESERVED_ENTITIES).length === 0,
assert(intersection(actionNames, RESERVED_ACTION_NAMES).length === 0,
`${filename}中的Action命名不能是「${RESERVED_ACTION_NAMES.join(',')}」之一`);
node.types.forEach(
@ -6852,7 +6852,7 @@ export function analyzeEntities(inputDir: string, relativePath?: string) {
const fullFilenames = files.map(
ele => {
const entity = ele.slice(0, ele.indexOf('.'))
if (RESERVED_ENTITIES.includes(entity) || RESERVED_ENTITIES.find(
if (RESERVED_ENTITY_NAMES.includes(entity) || RESERVED_ENTITY_NAMES.find(
ele2 => entity.startsWith(ele2)
)) {
throw new Error(`${ele}是系统保留字,请勿使用其当对象名或对象名前缀`);

View File

@ -16,6 +16,7 @@ import { getRelevantIds } from "./filter";
import { CreateSingleOperation as CreateSingleOperOperation } from '../base-app-domain/Oper/Schema';
import { CreateOperation as CreateModiOperation, UpdateOperation as UpdateModiOperation } from '../base-app-domain/Modi/Schema';
import { generateNewIdAsync } from "../utils/uuid";
import { SYSTEM_RESERVE_ENTITIES } from "../compiler/env";
/**这个用来处理级联的select和update对不同能力的 */
export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> extends RowStore<ED> {
@ -25,10 +26,50 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
protected abstract supportManyToOneJoin(): boolean;
protected abstract supportMultipleCreate(): boolean;
private selectionRewriters: SelectionRewriter<any>[] = [];
private operationRewriters: OperationRewriter<any>[] = [];
private selectionRewriters: SelectionRewriter<ED, AsyncContext<ED>>[] = [];
private operationRewriters: OperationRewriter<ED, AsyncContext<ED>>[] = [];
private reinforceSelection(entity: keyof ED, selection: ED[keyof ED]['Selection']) {
private async reinforceSelection<Cxt extends AsyncContext<ED>, OP extends SelectOption>(entity: keyof ED, selection: ED[keyof ED]['Selection'], context: Cxt, option: OP) {
const noRelationDestEntities: string[] = [];
this.reinforceSelectionInner(entity, selection, context, noRelationDestEntities);
const rewriterPromises: Promise<any>[] = this.selectionRewriters.map(
ele => ele(this.getSchema(), entity, selection, context)
);
// 这个设计每次都要取actionAuth的数据感觉不是很优雅。by Xc 20230722
if (noRelationDestEntities.length > 0 && !option.dontCollect) {
rewriterPromises.push(
context.select('actionAuth', {
data: {
id: 1,
deActions: 1,
destEntity: 1,
path: 1,
relationId: 1,
},
filter: {
relationId: {
$exists: false,
},
destEntity: {
$in: noRelationDestEntities,
},
},
}, {})
);
}
if (rewriterPromises.length > 0) {
await Promise.all(rewriterPromises);
}
}
private reinforceSelectionInner<Cxt extends AsyncContext<ED>, OP extends SelectOption>(
entity: keyof ED,
selection: ED[keyof ED]['Selection'],
context: Cxt,
noRelationDestEntities: string[]) {
const { filter, data, sorter } = selection;
const checkNode = (projectionNode: ED[keyof ED]['Selection']['data'], attrs: string[]) => {
@ -148,7 +189,7 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
const toBeAssignNode2: Record<string, string[]> = {}; // 用来记录在表达式中涉及到的结点
const projectionNodeDict: Record<string, ED[keyof ED]['Selection']['data']> = {};
const checkProjectionNode = (entity2: keyof ED, projectionNode: ED[keyof ED]['Selection']['data']) => {
const checkProjectionNode = async (entity2: keyof ED, projectionNode: ED[keyof ED]['Selection']['data']) => {
const necessaryAttrs: string[] = ['id', '$$createAt$$']; // 有的页面依赖于其它页面取数据有时两个页面的filter的差异会导致有一个加createAt有一个不加此时可能产生前台取数据不完整的异常。先统一加上
for (const attr in projectionNode) {
if (attr === '#id') {
@ -204,7 +245,7 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
else {
checkNode(data, ['entity', 'entityId']);
}
this.reinforceSelection(rel[0], projectionNode[attr]);
this.reinforceSelectionInner(rel[0], projectionNode[attr], context, noRelationDestEntities);
}
}
}
@ -232,6 +273,46 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
}
});
}
// 如果对象上有relation关系在此将本用户相关的relation和actionAuth全部取出
// 还要将actionAuth上没有relation关系但destEntity为本对象的行也全部取出这些是指向userId的可能路径
// 放在这里有点怪异,暂先这样
const userId = context.getCurrentUserId(true);
if (userId && !SYSTEM_RESERVE_ENTITIES.includes(entity2 as string)) {
if (this.getSchema()[entity2].relation && !projectionNode.userRelation$entity) {
Object.assign(projectionNode, {
userRelation$entity: {
$entity: 'userRelation',
data: {
id: 1,
entity: 1,
entityId: 1,
userId: 1,
relationId: 1,
relation: {
id: 1,
name: 1,
display: 1,
actionAuth$relation: {
$entity: 'actionAuth',
data: {
id: 1,
deActions: 1,
destEntity: 1,
path: 1,
relationId: 1,
},
}
}
},
filter: {
userId,
},
} as ED['userRelation']['Selection'],
});
}
noRelationDestEntities.push(entity2 as string);
}
};
checkProjectionNode(entity, data);
@ -252,22 +333,19 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
});
}
this.selectionRewriters.forEach(
ele => ele(this.getSchema(), entity, selection)
);
}
private reinforceOperation(entity: keyof ED, operation: ED[keyof ED]['Operation']) {
this.operationRewriters.forEach(
ele => ele(this.getSchema(), entity, operation)
);
private async reinforceOperation<Cxt extends AsyncContext<ED>>(entity: keyof ED, operation: ED[keyof ED]['Operation'], context: Cxt) {
await Promise.all(this.operationRewriters.map(
ele => ele(this.getSchema(), entity, operation, context)
));
}
public registerOperationRewriter(rewriter: OperationRewriter<ED>) {
public registerOperationRewriter(rewriter: OperationRewriter<ED, AsyncContext<ED>>) {
this.operationRewriters.push(rewriter);
}
public registerSelectionRewriter(rewriter: SelectionRewriter<ED>) {
public registerSelectionRewriter(rewriter: SelectionRewriter<ED, AsyncContext<ED>>) {
this.selectionRewriters.push(rewriter);
}
@ -1705,7 +1783,6 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
operation: ED[T]['Operation'],
context: Cxt,
option: OP): Promise<OperationResult<ED>> {
this.reinforceOperation(entity, operation);
const { action, data, filter, id } = operation;
let opData: any;
const wholeBeforeFns: Array<() => Promise<any>> = [];
@ -1768,7 +1845,6 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
selection: ED[T]['Selection'],
context: Cxt,
option: OP): Partial<ED[T]['Schema']>[] {
this.reinforceSelection(entity, selection);
const { data, filter, indexFrom, count, sorter } = selection;
const { projection, cascadeSelectionFns } = this.destructCascadeSelect(
entity,
@ -1967,7 +2043,7 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
selection: ED[T]['Selection'],
context: Cxt,
option: OP): Promise<Partial<ED[T]['Schema']>[]> {
this.reinforceSelection(entity, selection);
await this.reinforceSelection(entity, selection, context, option);
return this.cascadeSelectAsync(entity, selection, context, option);
}
@ -1989,12 +2065,12 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
return this.cascadeUpdate(entity, operation, context, option);
}
protected operateAsync<T extends keyof ED, Cxt extends AsyncContext<ED>, OP extends OperateOption>(
protected async operateAsync<T extends keyof ED, Cxt extends AsyncContext<ED>, OP extends OperateOption>(
entity: T,
operation: ED[T]['Operation'],
context: Cxt,
option: OP): Promise<OperationResult<ED>> {
this.reinforceOperation(entity, operation);
return this.operateAsync(entity, operation, context, option);
await this.reinforceOperation(entity, operation, context);
return this.cascadeUpdateAsync(entity, operation, context, option);
}
}

View File

@ -8,6 +8,7 @@ import { judgeRelation } from "./relation";
import { SyncContext } from "./SyncRowStore";
import { readOnlyActions } from '../actions/action';
import { difference, intersection, set } from '../utils/lodash';
import { SYSTEM_RESERVE_ENTITIES } from "../compiler/env";
type OperationTree<ED extends EntityDict & BaseEntityDict> = {
@ -36,7 +37,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
private relationCascadePathGraph: AuthCascadePath<ED>[];
private authDeduceRelationMap: AuthDeduceRelationMap<ED>;
private schema: StorageSchema<ED>;
static SPECIAL_ENTITIES = ['user', 'relation', 'oper', 'operEntity', 'modi', 'modiEntity', 'userRelation', 'actionAuth', 'relationAuth', 'relation'];
static SPECIAL_ENTITIES = SYSTEM_RESERVE_ENTITIES;
/**
* actionsoverlap关系relationId和relativePath
*/

View File

@ -1,13 +1,24 @@
import { OperationResult, EntityDict } from './Entity';
import { StorageSchema } from './Storage';
import { get, set } from '../utils/lodash';
import { AsyncContext } from '../store/AsyncRowStore';
export type TxnOption = {
isolationLevel: 'repeatable read' | 'serializable';
};
export type SelectionRewriter<ED extends EntityDict> = (schema: StorageSchema<ED>, entity: keyof ED, selection: ED[keyof ED]['Selection']) => void;
export type OperationRewriter<ED extends EntityDict> = (schema: StorageSchema<ED>, entity: keyof ED, operate: ED[keyof ED]['Operation']) => void;
export type SelectionRewriter<ED extends EntityDict, Cxt extends AsyncContext<ED>> = (
schema: StorageSchema<ED>,
entity: keyof ED,
selection: ED[keyof ED]['Selection'],
context: Cxt
) => Promise<void>;
export type OperationRewriter<ED extends EntityDict, Cxt extends AsyncContext<ED>> = (
schema: StorageSchema<ED>,
entity: keyof ED,
operate: ED[keyof ED]['Operation'],
context: Cxt
) => Promise<void>;
export abstract class RowStore<ED extends EntityDict> {
@ -17,14 +28,14 @@ export abstract class RowStore<ED extends EntityDict> {
this.storageSchema = storageSchema;
}
abstract registerOperationRewriter(rewriter: OperationRewriter<ED>): void;
abstract registerOperationRewriter(rewriter: OperationRewriter<ED, AsyncContext<ED>>): void;
abstract registerSelectionRewriter(rewriter: SelectionRewriter<ED>): void;
abstract registerSelectionRewriter(rewriter: SelectionRewriter<ED, AsyncContext<ED>>): void;
getSchema () {
getSchema() {
return this.storageSchema;
}
mergeOperationResult(result: OperationResult<ED>, toBeMerged: OperationResult<ED>) {
for (const entity in toBeMerged) {
for (const action in toBeMerged[entity]) {