diff --git a/lib/base-app-domain/Modi/Schema.d.ts b/lib/base-app-domain/Modi/Schema.d.ts index 915b675..30dc913 100644 --- a/lib/base-app-domain/Modi/Schema.d.ts +++ b/lib/base-app-domain/Modi/Schema.d.ts @@ -5,7 +5,6 @@ import * as SubQuery from "../_SubQuery"; import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, EntityShape, AggregationResult } from "../../types/Entity"; import { Action, ParticularAction, IState } from "./Action"; import * as ModiEntity from "../ModiEntity/Schema"; -import * as OperEntity from "../OperEntity/Schema"; export declare type OpSchema = EntityShape & { targetEntity: String<32>; entity: String<32>; @@ -28,8 +27,6 @@ export declare type Schema = EntityShape & { iState?: IState | null; modiEntity$modi?: Array; modiEntity$modi$$aggr?: AggregationResult; - operEntity$entity?: Array; - operEntity$entity$$aggr?: AggregationResult; } & { [A in ExpressionKey]?: any; }; @@ -69,12 +66,6 @@ export declare type Projection = { modiEntity$modi$$aggr?: ModiEntity.Aggregation & { $entity: "modiEntity"; }; - operEntity$entity?: OperEntity.Selection & { - $entity: "operEntity"; - }; - operEntity$entity$$aggr?: OperEntity.Aggregation & { - $entity: "operEntity"; - }; } & Partial>; declare type ModiIdProjection = OneOf<{ id: number; @@ -114,7 +105,6 @@ export declare type CreateOperationData = FormCreateData[]> | Array>>; - operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; }; export declare type CreateSingleOperation = OakOperation<"create", CreateOperationData>; export declare type CreateMultipleOperation = OakOperation<"create", Array>; @@ -122,7 +112,6 @@ export declare type CreateOperation = CreateSingleOperation | CreateMultipleOper export declare type UpdateOperationData = FormUpdateData & { [k: string]: any; modiEntity$modi?: OakOperation<"create", Omit[]> | Array>>; - operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; }; export declare type UpdateOperation = OakOperation<"update" | ParticularAction | string, UpdateOperationData, Filter, Sorter>; export declare type RemoveOperationData = {}; diff --git a/lib/base-app-domain/Oper/Schema.d.ts b/lib/base-app-domain/Oper/Schema.d.ts index 5a554b6..781eb05 100644 --- a/lib/base-app-domain/Oper/Schema.d.ts +++ b/lib/base-app-domain/Oper/Schema.d.ts @@ -12,6 +12,7 @@ export declare type OpSchema = EntityShape & { filter?: Object | null; extra?: Object | null; operatorId?: ForeignKey<"user"> | null; + targetEntity: String<32>; }; export declare type OpAttr = keyof OpSchema; export declare type Schema = EntityShape & { @@ -20,6 +21,7 @@ export declare type Schema = EntityShape & { filter?: Object | null; extra?: Object | null; operatorId?: ForeignKey<"user"> | null; + targetEntity: String<32>; operator?: User.Schema | null; operEntity$oper?: Array; operEntity$oper$$aggr?: AggregationResult; @@ -37,6 +39,7 @@ declare type AttrFilter = { extra: Object; operatorId: Q_StringValue | SubQuery.UserIdSubQuery; operator: User.Filter; + targetEntity: Q_StringValue; }; export declare type Filter = MakeFilter>; export declare type Projection = { @@ -52,6 +55,7 @@ export declare type Projection = { extra?: number; operatorId?: number; operator?: User.Projection; + targetEntity?: number; operEntity$oper?: OperEntity.Selection & { $entity: "operEntity"; }; @@ -79,6 +83,8 @@ export declare type SortAttr = { operatorId: number; } | { operator: User.SortAttr; +} | { + targetEntity: number; } | { [k: string]: any; } | OneOf>; diff --git a/lib/base-app-domain/Oper/Storage.js b/lib/base-app-domain/Oper/Storage.js index b21ecf3..a440ae5 100644 --- a/lib/base-app-domain/Oper/Storage.js +++ b/lib/base-app-domain/Oper/Storage.js @@ -22,6 +22,12 @@ exports.desc = { operatorId: { type: "ref", ref: "user" + }, + targetEntity: { + type: "varchar", + params: { + length: 32 + } } }, actionType: "appendOnly", diff --git a/lib/base-app-domain/OperEntity/Schema.d.ts b/lib/base-app-domain/OperEntity/Schema.d.ts index 8497388..45e8505 100644 --- a/lib/base-app-domain/OperEntity/Schema.d.ts +++ b/lib/base-app-domain/OperEntity/Schema.d.ts @@ -5,21 +5,19 @@ import * as SubQuery from "../_SubQuery"; import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, EntityShape } from "../../types/Entity"; import { AppendOnlyAction } from "../../actions/action"; import * as Oper from "../Oper/Schema"; -import * as Modi from "../Modi/Schema"; import * as User from "../User/Schema"; import * as UserEntityGrant from "../UserEntityGrant/Schema"; export declare type OpSchema = EntityShape & { operId: ForeignKey<"oper">; - entity: "modi" | "user" | "userEntityGrant" | string; + entity: "user" | "userEntityGrant" | string; entityId: String<64>; }; export declare type OpAttr = keyof OpSchema; export declare type Schema = EntityShape & { operId: ForeignKey<"oper">; - entity: "modi" | "user" | "userEntityGrant" | string; + entity: "user" | "userEntityGrant" | string; entityId: String<64>; oper: Oper.Schema; - modi?: Modi.Schema; user?: User.Schema; userEntityGrant?: UserEntityGrant.Schema; } & { @@ -34,11 +32,10 @@ declare type AttrFilter = { oper: Oper.Filter; entity: E; entityId: Q_StringValue; - modi: Modi.Filter; user: User.Filter; userEntityGrant: UserEntityGrant.Filter; }; -export declare type Filter> = MakeFilter & ExprOp>; +export declare type Filter> = MakeFilter & ExprOp>; export declare type Projection = { "#id"?: NodeId; [k: string]: any; @@ -50,7 +47,6 @@ export declare type Projection = { oper?: Oper.Projection; entity?: number; entityId?: number; - modi?: Modi.Projection; user?: User.Projection; userEntityGrant?: UserEntityGrant.Projection; } & Partial>; @@ -60,9 +56,6 @@ declare type OperEntityIdProjection = OneOf<{ declare type OperIdProjection = OneOf<{ operId: number; }>; -declare type ModiIdProjection = OneOf<{ - entityId: number; -}>; declare type UserIdProjection = OneOf<{ entityId: number; }>; @@ -85,8 +78,6 @@ export declare type SortAttr = { entity: number; } | { entityId: number; -} | { - modi: Modi.SortAttr; } | { user: User.SortAttr; } | { @@ -108,17 +99,6 @@ export declare type CreateOperationData = FormCreateData; })) & ({ - entity?: never; - entityId?: never; - modi: Modi.CreateSingleOperation; -} | { - entity: "modi"; - entityId: String<64>; - modi: Modi.UpdateOperation; -} | { - entity: "modi"; - entityId: String<64>; -} | { entity?: never; entityId?: never; user: User.CreateSingleOperation; @@ -155,10 +135,6 @@ export declare type UpdateOperationData = FormUpdateData | null; })) & ({ - modi?: Modi.CreateSingleOperation | Modi.UpdateOperation | Modi.RemoveOperation; - entityId?: never; - entity?: never; -} | { user?: User.CreateSingleOperation | User.UpdateOperation | User.RemoveOperation; entityId?: never; entity?: never; @@ -167,15 +143,13 @@ export declare type UpdateOperationData = FormUpdateData | null; }) & { [k: string]: any; }; export declare type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>; export declare type RemoveOperationData = {} & ({ - modi?: Modi.UpdateOperation | Modi.RemoveOperation; -} | { user?: User.UpdateOperation | User.RemoveOperation; } | { userEntityGrant?: UserEntityGrant.UpdateOperation | UserEntityGrant.RemoveOperation; @@ -185,7 +159,6 @@ export declare type RemoveOperationData = {} & ({ export declare type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; export declare type Operation = CreateOperation | UpdateOperation | RemoveOperation; export declare type OperIdSubQuery = Selection; -export declare type ModiIdSubQuery = Selection; export declare type UserIdSubQuery = Selection; export declare type UserEntityGrantIdSubQuery = Selection; export declare type OperEntityIdSubQuery = Selection; diff --git a/lib/base-app-domain/OperEntity/Storage.js b/lib/base-app-domain/OperEntity/Storage.js index 8d775ea..2ee6a26 100644 --- a/lib/base-app-domain/OperEntity/Storage.js +++ b/lib/base-app-domain/OperEntity/Storage.js @@ -13,7 +13,7 @@ exports.desc = { params: { length: 32 }, - ref: ["modi", "user", "userEntityGrant"] + ref: ["user", "userEntityGrant"] }, entityId: { type: "varchar", diff --git a/lib/base-app-domain/User/Schema.d.ts b/lib/base-app-domain/User/Schema.d.ts index 6f2d79c..74435ac 100644 --- a/lib/base-app-domain/User/Schema.d.ts +++ b/lib/base-app-domain/User/Schema.d.ts @@ -6,8 +6,8 @@ import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOper import { Action, ParticularAction, UserState } from "./Action"; import { RelationAction } from "../../actions/action"; import * as Oper from "../Oper/Schema"; -import * as OperEntity from "../OperEntity/Schema"; import * as ModiEntity from "../ModiEntity/Schema"; +import * as OperEntity from "../OperEntity/Schema"; export declare type OpSchema = EntityShape & { name?: String<16> | null; nickname?: String<64> | null; @@ -27,10 +27,10 @@ export declare type Schema = EntityShape & { oper$operator$$aggr?: AggregationResult; user$ref?: Array; user$ref$$aggr?: AggregationResult; - operEntity$entity?: Array; - operEntity$entity$$aggr?: AggregationResult; modiEntity$entity?: Array; modiEntity$entity$$aggr?: AggregationResult; + operEntity$entity?: Array; + operEntity$entity$$aggr?: AggregationResult; } & { [A in ExpressionKey]?: any; }; @@ -72,18 +72,18 @@ export declare type Projection = { user$ref$$aggr?: Aggregation & { $entity: "user"; }; - operEntity$entity?: OperEntity.Selection & { - $entity: "operEntity"; - }; - operEntity$entity$$aggr?: OperEntity.Aggregation & { - $entity: "operEntity"; - }; modiEntity$entity?: ModiEntity.Selection & { $entity: "modiEntity"; }; modiEntity$entity$$aggr?: ModiEntity.Aggregation & { $entity: "modiEntity"; }; + operEntity$entity?: OperEntity.Selection & { + $entity: "operEntity"; + }; + operEntity$entity$$aggr?: OperEntity.Aggregation & { + $entity: "operEntity"; + }; } & Partial>; declare type UserIdProjection = OneOf<{ id: number; @@ -131,8 +131,8 @@ export declare type CreateOperationData = FormCreateData })) & { oper$operator?: OakOperation<"create", Omit[]> | Array>>; user$ref?: OakOperation, Filter> | OakOperation<"create", Omit[]> | Array> | OakOperation, Filter>>; - operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; modiEntity$entity?: OakOperation<"create", Omit[]> | Array>>; + operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; }; export declare type CreateSingleOperation = OakOperation<"create", CreateOperationData>; export declare type CreateMultipleOperation = OakOperation<"create", Array>; @@ -153,8 +153,8 @@ export declare type UpdateOperationData = FormUpdateData [k: string]: any; oper$operator?: OakOperation<"create", Omit[]> | Array>>; user$ref?: UpdateOperation | RemoveOperation | OakOperation<"create", Omit[]> | Array> | UpdateOperation | RemoveOperation>; - operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; modiEntity$entity?: OakOperation<"create", Omit[]> | Array>>; + operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; }; export declare type UpdateOperation = OakOperation<"update" | ParticularAction | RelationAction | string, UpdateOperationData, Filter, Sorter>; export declare type RemoveOperationData = {} & (({ diff --git a/lib/base-app-domain/UserEntityGrant/Schema.d.ts b/lib/base-app-domain/UserEntityGrant/Schema.d.ts index 6e2d17b..6334c2e 100644 --- a/lib/base-app-domain/UserEntityGrant/Schema.d.ts +++ b/lib/base-app-domain/UserEntityGrant/Schema.d.ts @@ -4,8 +4,8 @@ import { OneOf } from "../../types/Polyfill"; import * as SubQuery from "../_SubQuery"; import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, EntityShape, AggregationResult } from "../../types/Entity"; import { GenericAction } from "../../actions/action"; -import * as OperEntity from "../OperEntity/Schema"; import * as ModiEntity from "../ModiEntity/Schema"; +import * as OperEntity from "../OperEntity/Schema"; export declare type OpSchema = EntityShape & { entity: String<32>; entityId: String<64>; @@ -16,10 +16,10 @@ export declare type Schema = EntityShape & { entity: String<32>; entityId: String<64>; relation: String<32>; - operEntity$entity?: Array; - operEntity$entity$$aggr?: AggregationResult; modiEntity$entity?: Array; modiEntity$entity$$aggr?: AggregationResult; + operEntity$entity?: Array; + operEntity$entity$$aggr?: AggregationResult; } & { [A in ExpressionKey]?: any; }; @@ -43,18 +43,18 @@ export declare type Projection = { entity?: number; entityId?: number; relation?: number; - operEntity$entity?: OperEntity.Selection & { - $entity: "operEntity"; - }; - operEntity$entity$$aggr?: OperEntity.Aggregation & { - $entity: "operEntity"; - }; modiEntity$entity?: ModiEntity.Selection & { $entity: "modiEntity"; }; modiEntity$entity$$aggr?: ModiEntity.Aggregation & { $entity: "modiEntity"; }; + operEntity$entity?: OperEntity.Selection & { + $entity: "operEntity"; + }; + operEntity$entity$$aggr?: OperEntity.Aggregation & { + $entity: "operEntity"; + }; } & Partial>; declare type UserEntityGrantIdProjection = OneOf<{ id: number; @@ -89,16 +89,16 @@ export declare type CreateOperationData = FormCreateData[]> | Array>>; modiEntity$entity?: OakOperation<"create", Omit[]> | Array>>; + operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; }; export declare type CreateSingleOperation = OakOperation<"create", CreateOperationData>; export declare type CreateMultipleOperation = OakOperation<"create", Array>; export declare type CreateOperation = CreateSingleOperation | CreateMultipleOperation; export declare type UpdateOperationData = FormUpdateData & { [k: string]: any; - operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; modiEntity$entity?: OakOperation<"create", Omit[]> | Array>>; + operEntity$entity?: OakOperation<"create", Omit[]> | Array>>; }; export declare type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>; export declare type RemoveOperationData = {}; diff --git a/lib/compiler/env.d.ts b/lib/compiler/env.d.ts index 8d62545..2529d76 100644 --- a/lib/compiler/env.d.ts +++ b/lib/compiler/env.d.ts @@ -5,6 +5,7 @@ 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 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; diff --git a/lib/compiler/env.js b/lib/compiler/env.js index 0bf57cc..3fc8d64 100644 --- a/lib/compiler/env.js +++ b/lib/compiler/env.js @@ -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.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.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.LIB_OAK_DOMAIN = 'oak-domain'; var LIB_OAK_GENERAL_BUSINESS = 'oak-general-business'; var LIB_PATH = function () { return 'lib'; }; @@ -33,6 +33,7 @@ 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', '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; diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index e309cc2..2ce4d2d 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -344,6 +344,18 @@ function getStringEnumValues(filename, program, obj, node) { } } } +function checkNameLegal(filename, attrName, upperCase) { + (0, assert_1.default)(attrName.length <= env_1.ENTITY_NAME_MAX_LENGTH, "\u6587\u4EF6\u300C".concat(filename, "\u300D\uFF1A\u300C").concat(attrName, "\u300D\u7684\u540D\u79F0\u5B9A\u4E49\u8FC7\u957F\uFF0C\u4E0D\u80FD\u8D85\u8FC7\u300C").concat(env_1.ENTITY_NAME_MAX_LENGTH, "\u300D\u957F\u5EA6")); + if (upperCase) { + (0, assert_1.default)(/[A-Z][a-z|A-Z|0-9]+/i.test(attrName), "\u6587\u4EF6\u300C".concat(filename, "\u300D\uFF1A\u300C").concat(attrName, "\u300D\u7684\u540D\u79F0\u5FC5\u987B\u4EE5\u5927\u5199\u5B57\u6BCD\u5F00\u59CB\uFF0C\u4E14\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u548C\u6570\u5B57")); + } + else if (upperCase === false) { + (0, assert_1.default)(/[a-z][a-z|A-Z|0-9]+/i.test(attrName), "\u6587\u4EF6\u300C".concat(filename, "\u300D\uFF1A\u300C").concat(attrName, "\u300D\u7684\u540D\u79F0\u5FC5\u987B\u4EE5\u5C0F\u5199\u5B57\u6BCD\u5F00\u59CB\uFF0C\u4E14\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u548C\u6570\u5B57")); + } + else { + (0, assert_1.default)(/[a-z|A-Z][a-z|A-Z|0-9]+/i.test(attrName), "\u6587\u4EF6\u300C".concat(filename, "\u300D\uFF1A\u300C").concat(attrName, "\u300D\u7684\u540D\u79F0\u5FC5\u987B\u4EE5\u5B57\u6BCD\u5F00\u59CB\uFF0C\u4E14\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u548C\u6570\u5B57")); + } +} function analyzeEntity(filename, path, program, relativePath) { var _a; var fullPath = "".concat(path, "/").concat(filename); @@ -355,6 +367,7 @@ function analyzeEntity(filename, path, program, relativePath) { // removeFromRelationShip(moduleName); console.warn("\u51FA\u73B0\u4E86\u540C\u540D\u7684Entity\u5B9A\u4E49\u300C".concat(moduleName, "\u300D\uFF0C\u5C06\u4F7F\u7528").concat(fullPath, "\u53D6\u4EE3\u6389\u9ED8\u8BA4\u5BF9\u8C61\uFF0C\u8BF7\u68C0\u67E5\u65B0\u7684\u5BF9\u8C61\u7ED3\u6784\u53CA\u76F8\u5173\u5E38\u91CF\u5B9A\u4E49\u4E0E\u539F\u6709\u7684\u517C\u5BB9\uFF0C\u5426\u5219\u539F\u6709\u5BF9\u8C61\u7684\u76F8\u5173\u903B\u8F91\u4F1A\u51FA\u73B0\u4E0D\u53EF\u77E5\u5F02\u5E38")); } + checkNameLegal(filename, moduleName, true); var referencedSchemas = []; var schemaAttrs = []; var hasFulltextIndex = false; @@ -406,6 +419,7 @@ function analyzeEntity(filename, path, program, relativePath) { var _a, _b; var _c = attrNode, type = _c.type, name = _c.name, questionToken = _c.questionToken; var attrName = name.text; + checkNameLegal(filename, attrName, false); if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) { if ((referencedSchemas.includes(type.typeName.text) || type.typeName.text === 'Schema')) { @@ -516,27 +530,27 @@ function analyzeEntity(filename, path, program, relativePath) { _a[moduleName] = 1, _a)); } + else if (hasEntityAttr_1 || hasEntityIdAttr_1) { + throw new Error("\u6587\u4EF6\u300C".concat(filename, "\u300D\uFF1A\u5C5E\u6027 \u5B9A\u4E49\u4E2D\u53EA\u5305\u542B").concat(hasEntityAttr_1 ? 'entity' : 'entityId', "\uFF0C\u4E0D\u7B26\u5408\u5B9A\u4E49\u89C4\u8303\u3002entity/entityId\u5FC5\u987B\u8054\u5408\u51FA\u73B0\uFF0C\u4EE3\u8868\u4E0D\u5B9A\u5BF9\u8C61\u7684\u53CD\u5411\u6307\u9488")); + } beforeSchema = false; - // 对于不是Oper的对象,全部建立和OperEntity的反指关系 - if (!['Oper', 'OperEntity', 'ModiEntity'].includes(moduleName)) { + // 对于不是Modi和Oper的对象,全部建立和ModiEntity的反指关系 + if (!['Modi', 'Oper', 'OperEntity', 'ModiEntity'].includes(moduleName) && !toModi) { + if (ReversePointerRelations['ModiEntity'] && !ReversePointerRelations['ModiEntity'].includes(moduleName)) { + ReversePointerRelations['ModiEntity'].push(moduleName); + } + else { + (0, lodash_1.assign)(ReversePointerRelations, (_b = {}, + _b['ModiEntity'] = [moduleName], + _b)); + } if (ReversePointerRelations['OperEntity'] && !ReversePointerRelations['OperEntity'].includes(moduleName)) { ReversePointerRelations['OperEntity'].push(moduleName); } else { - (0, lodash_1.assign)(ReversePointerRelations, (_b = {}, - _b['OperEntity'] = [moduleName], - _b)); - } - // 对于不是Modi的对象,全部建立和ModiEntity的反指关系 - if (!['Modi'].includes(moduleName) && !toModi) { - if (ReversePointerRelations['ModiEntity'] && !ReversePointerRelations['ModiEntity'].includes(moduleName)) { - ReversePointerRelations['ModiEntity'].push(moduleName); - } - else { - (0, lodash_1.assign)(ReversePointerRelations, (_c = {}, - _c['ModiEntity'] = [moduleName], - _c)); - } + (0, lodash_1.assign)(ReversePointerRelations, (_c = {}, + _c['OperEntity'] = [moduleName], + _c)); } } } diff --git a/lib/entities/Oper.d.ts b/lib/entities/Oper.d.ts index 3bc1323..039e743 100644 --- a/lib/entities/Oper.d.ts +++ b/lib/entities/Oper.d.ts @@ -7,4 +7,5 @@ export interface Schema extends EntityShape { filter?: Object; extra?: Object; operator?: User; + targetEntity: String<32>; } diff --git a/lib/entities/Oper.js b/lib/entities/Oper.js index 7050f0d..5fb9bdb 100644 --- a/lib/entities/Oper.js +++ b/lib/entities/Oper.js @@ -13,6 +13,7 @@ var locale = { filter: '选择条件', extra: '其它', operator: '操作者', + targetEntity: '关联对象', }, }, }; diff --git a/lib/store/AsyncRowStore.d.ts b/lib/store/AsyncRowStore.d.ts index c5a0247..024e835 100644 --- a/lib/store/AsyncRowStore.d.ts +++ b/lib/store/AsyncRowStore.d.ts @@ -2,7 +2,7 @@ import { EntityDict, RowStore, OperateOption, OperationResult, SelectOption, Context, TxnOption, OpRecord, AggregationResult } from "../types"; import { IncomingHttpHeaders } from "http"; export declare abstract class AsyncContext implements Context { - private rowStore; + rowStore: AsyncRowStore; private uuid?; opRecords: OpRecord[]; private scene?; @@ -12,6 +12,10 @@ export declare abstract class AsyncContext implements Con commit: Array<() => Promise>; rollback: Array<() => Promise>; }; + /** + * 在返回结果前调用,对数据行进行一些预处理,比如将一些敏感的列隐藏 + */ + abstract refineOpRecords(): Promise; constructor(store: AsyncRowStore>, headers?: IncomingHttpHeaders); setHeaders(headers: IncomingHttpHeaders): void; getHeader(key: string): string | string[] | undefined; diff --git a/lib/store/CascadeStore.d.ts b/lib/store/CascadeStore.d.ts index c8346eb..ef9fef7 100644 --- a/lib/store/CascadeStore.d.ts +++ b/lib/store/CascadeStore.d.ts @@ -1,6 +1,6 @@ import { EntityDict, OperateOption, SelectOption, OperationResult, AggregationResult } from "../types/Entity"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; -import { RowStore } from '../types/RowStore'; +import { OperationRewriter, RowStore, SelectionRewriter } from '../types/RowStore'; import { StorageSchema } from '../types/Storage'; import { SyncContext } from "./SyncRowStore"; import { AsyncContext } from "./AsyncRowStore"; @@ -9,6 +9,12 @@ export declare abstract class CascadeStore); protected abstract supportManyToOneJoin(): boolean; protected abstract supportMultipleCreate(): boolean; + private selectionRewriters; + private operationRewriters; + private reinforceSelection; + private reinforceOperation; + registerOperationRewriter(rewriter: OperationRewriter): void; + registerSelectionRewriter(rewriter: SelectionRewriter): void; protected abstract selectAbjointRow>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Partial[]; protected abstract updateAbjointRow>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: OP): number; protected abstract selectAbjointRowAsync>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: OP): Promise[]>; diff --git a/lib/store/CascadeStore.js b/lib/store/CascadeStore.js index e3ecab9..cf1216b 100644 --- a/lib/store/CascadeStore.js +++ b/lib/store/CascadeStore.js @@ -11,13 +11,251 @@ var types_1 = require("../types"); var lodash_1 = require("../utils/lodash"); var filter_2 = require("./filter"); var uuid_1 = require("../utils/uuid"); -var selection_1 = require("./selection"); /**这个用来处理级联的select和update,对不同能力的 */ var CascadeStore = /** @class */ (function (_super) { tslib_1.__extends(CascadeStore, _super); function CascadeStore(storageSchema) { - return _super.call(this, storageSchema) || this; + var _this = _super.call(this, storageSchema) || this; + _this.selectionRewriters = []; + _this.operationRewriters = []; + return _this; } + CascadeStore.prototype.reinforceSelection = function (entity, selection) { + var _this = this; + var filter = selection.filter, data = selection.data, sorter = selection.sorter; + var checkNode = function (projectionNode, attrs) { + attrs.forEach(function (attr) { + var _a; + if (!projectionNode.hasOwnProperty(attr)) { + Object.assign(projectionNode, (_a = {}, + _a[attr] = 1, + _a)); + } + }); + }; + var relevantIds = []; + if (filter) { + var toBeAssignNode_1 = {}; // 用来记录在表达式中涉及到的结点 + // filter当中所关联到的属性必须在projection中 + var filterNodeDict_1 = {}; + var checkFilterNode_1 = function (entity2, filterNode, projectionNode) { + var _a, e_1, _b, _c, _d, _e, _f; + var necessaryAttrs = ['id']; + for (var attr in filterNode) { + if (attr === '#id') { + (0, assert_1.default)(!filterNodeDict_1[filterNode[attr]], "projection\u4E2D\u7ED3\u70B9\u7684id\u6709\u91CD\u590D, ".concat(filterNode[attr])); + Object.assign(filterNodeDict_1, (_a = {}, + _a[filterNode[attr]] = projectionNode, + _a)); + if (toBeAssignNode_1[filterNode[attr]]) { + checkNode(projectionNode, toBeAssignNode_1[filterNode[attr]]); + } + } + else if (['$and', '$or'].includes(attr)) { + try { + for (var _g = (e_1 = void 0, tslib_1.__values(filterNode[attr])), _h = _g.next(); !_h.done; _h = _g.next()) { + var node = _h.value; + checkFilterNode_1(entity2, node, projectionNode); + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (_h && !_h.done && (_b = _g.return)) _b.call(_g); + } + finally { if (e_1) throw e_1.error; } + } + } + else if (attr === '$not') { + checkFilterNode_1(entity2, filterNode[attr], projectionNode); + } + else if (attr === '$text') { + // 全文检索首先要有fulltext索引,其次要把fulltext的相关属性加到projection里 + var indexes = _this.getSchema()[entity2].indexes; + var fulltextIndex = indexes.find(function (ele) { return ele.config && ele.config.type === 'fulltext'; }); + var attributes = fulltextIndex.attributes; + necessaryAttrs.push.apply(necessaryAttrs, tslib_1.__spreadArray([], tslib_1.__read((attributes.map(function (ele) { return ele.name; }))), false)); + } + else { + if (attr.toLowerCase().startsWith(types_1.EXPRESSION_PREFIX)) { + var exprResult = (0, types_1.getAttrRefInExpression)(filterNode[attr]); + for (var nodeName in exprResult) { + if (nodeName === '#current') { + checkNode(projectionNode, exprResult[nodeName]); + } + else if (filterNodeDict_1[nodeName]) { + checkNode(filterNodeDict_1[nodeName], exprResult[nodeName]); + } + else { + if (toBeAssignNode_1[nodeName]) { + (_c = toBeAssignNode_1[nodeName]).push.apply(_c, tslib_1.__spreadArray([], tslib_1.__read(exprResult[nodeName]), false)); + } + else { + Object.assign(toBeAssignNode_1, (_d = {}, + _d[nodeName] = exprResult[nodeName], + _d)); + } + } + } + } + else { + var rel = _this.judgeRelation(entity2, attr); + if (rel === 1) { + necessaryAttrs.push(attr); + } + else if (rel === 2) { + // entity/entityId反指 + necessaryAttrs.push('entity', 'entityId'); + if (!projectionNode[attr]) { + Object.assign(projectionNode, (_e = {}, + _e[attr] = { + id: 1, + }, + _e)); + } + checkFilterNode_1(attr, filterNode[attr], projectionNode[attr]); + } + else if (typeof rel === 'string') { + necessaryAttrs.push("".concat(attr, "Id")); + if (!projectionNode[attr]) { + Object.assign(projectionNode, (_f = {}, + _f[attr] = { + id: 1, + }, + _f)); + } + checkFilterNode_1(rel, filterNode[attr], projectionNode[attr]); + } + else if (rel instanceof Array) { + // 现在filter中还不支持一对多的语义,先放着吧 + } + } + } + checkNode(projectionNode, necessaryAttrs); + } + }; + checkFilterNode_1(entity, filter, data); + relevantIds = (0, filter_2.getRelevantIds)(filter); + } + // sorter感觉现在取不取影响不大,前端的list直接获取返回的ids了,先不管之 + if (sorter) { + } + var toBeAssignNode2 = {}; // 用来记录在表达式中涉及到的结点 + var projectionNodeDict = {}; + var checkProjectionNode = function (entity2, projectionNode) { + 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)); + } + } + } + } + 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]]); + } + else { + checkNode(data_1, ['entity', 'entityId']); + } + _this.reinforceSelection(rel[0], projectionNode[attr]); + } + } + } + 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', + }, + } + }); + } + }; + checkProjectionNode(entity, data); + if (!sorter && relevantIds.length === 0) { + // 如果没有sorter,就给予一个按createAt逆序的sorter + Object.assign(selection, { + sorter: [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'desc', + } + ] + }); + Object.assign(data, { + $$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.registerOperationRewriter = function (rewriter) { + this.operationRewriters.push(rewriter); + }; + CascadeStore.prototype.registerSelectionRewriter = function (rewriter) { + this.selectionRewriters.push(rewriter); + }; CascadeStore.prototype.destructCascadeSelect = function (entity, projection2, context, cascadeSelectFn, aggregateFn, option) { var _this = this; var projection = {}; @@ -69,6 +307,10 @@ var CascadeStore = /** @class */ (function (_super) { } else { cascadeSelectionFns.push(function (result) { + 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; + })); var dealWithSubRows = function (subRows) { (0, assert_1.default)(subRows.length <= entityIds.length); if (subRows.length < entityIds.length && !toModi) { @@ -101,10 +343,6 @@ var CascadeStore = /** @class */ (function (_super) { } }); }; - 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], @@ -121,8 +359,6 @@ var CascadeStore = /** @class */ (function (_super) { dealWithSubRows(subRows); } } - else { - } }); } } @@ -161,6 +397,7 @@ var CascadeStore = /** @class */ (function (_super) { } else { cascadeSelectionFns.push(function (result) { + var ids = (0, lodash_1.uniq)(result.filter(function (ele) { return !!(ele["".concat(attr, "Id")]); }).map(function (ele) { return ele["".concat(attr, "Id")]; })); var dealWithSubRows = function (subRows) { (0, assert_1.default)(subRows.length <= ids.length); if (subRows.length < ids.length && !toModi) { @@ -198,7 +435,6 @@ 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")]; })); if (ids.length > 0) { var subRows = cascadeSelectFn.call(_this, relation, { data: projection2[attr], @@ -262,14 +498,24 @@ var CascadeStore = /** @class */ (function (_super) { var _a; var ids = result.map(function (ele) { return ele.id; }); var dealWithSubRows = function (subRows) { - result.forEach(function (ele) { - var _a; - var subRowss = subRows.filter(function (ele2) { return ele2[foreignKey_1] === ele.id; }); - (0, assert_1.default)(subRowss); - Object.assign(ele, (_a = {}, - _a[attr] = subRowss, + var _a; + // 这里如果result只有一行,则把返回结果直接置上,不对比外键值 + // 这样做的原因是有的对象的filter会被改写掉(userId),只能临时这样处理 + if (result.length == 1) { + Object.assign(result[0], (_a = {}, + _a[attr] = subRows, _a)); - }); + } + else { + result.forEach(function (ele) { + var _a; + var subRowss = subRows.filter(function (ele2) { return ele2[foreignKey_1] === ele.id; }); + (0, assert_1.default)(subRowss); + Object.assign(ele, (_a = {}, + _a[attr] = subRowss, + _a)); + }); + } }; if (ids.length > 0) { var subRows = cascadeSelectFn.call(_this, entity2_1, { @@ -332,14 +578,24 @@ var CascadeStore = /** @class */ (function (_super) { cascadeSelectionFns.push(function (result) { var ids = result.map(function (ele) { return ele.id; }); var dealWithSubRows = function (subRows) { - result.forEach(function (ele) { - var _a; - var subRowss = subRows.filter(function (ele2) { return ele2.entityId === ele.id; }); - (0, assert_1.default)(subRowss); - Object.assign(ele, (_a = {}, - _a[attr] = subRowss, + var _a; + // 这里如果result只有一行,则把返回结果直接置上,不对比外键值 + // 这样做的原因是有的对象的filter会被改写掉(userId),只能临时这样处理 + if (result.length === 1) { + Object.assign(result[0], (_a = {}, + _a[attr] = subRows, _a)); - }); + } + else { + result.forEach(function (ele) { + var _a; + var subRowss = subRows.filter(function (ele2) { return ele2.entityId === ele.id; }); + (0, assert_1.default)(subRowss); + Object.assign(ele, (_a = {}, + _a[attr] = subRowss, + _a)); + }); + } }; if (ids.length > 0) { var subRows = cascadeSelectFn.call(_this, entity2_1, { @@ -426,7 +682,7 @@ var CascadeStore = /** @class */ (function (_super) { option2.modiParentEntity = entity; } var _loop_2 = function (attr) { - var _a, _b, _c, e_1, _d; + var _a, _b, _c, e_2, _d; var relation = (0, relation_1.judgeRelation)(this_2.storageSchema, entity, attr); if (relation === 1) { Object.assign(opData, (_a = {}, @@ -683,17 +939,17 @@ var CascadeStore = /** @class */ (function (_super) { }; if (otmOperations instanceof Array) { try { - for (var otmOperations_1 = (e_1 = void 0, tslib_1.__values(otmOperations)), otmOperations_1_1 = otmOperations_1.next(); !otmOperations_1_1.done; otmOperations_1_1 = otmOperations_1.next()) { + for (var otmOperations_1 = (e_2 = void 0, tslib_1.__values(otmOperations)), otmOperations_1_1 = otmOperations_1.next(); !otmOperations_1_1.done; otmOperations_1_1 = otmOperations_1.next()) { var oper = otmOperations_1_1.value; dealWithOneToMany(oper); } } - catch (e_1_1) { e_1 = { error: e_1_1 }; } + catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (otmOperations_1_1 && !otmOperations_1_1.done && (_d = otmOperations_1.return)) _d.call(otmOperations_1); } - finally { if (e_1) throw e_1.error; } + finally { if (e_2) throw e_2.error; } } } else { @@ -754,8 +1010,8 @@ 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, _d, createOper, updateAttrCount, result; - var e_2, _e, _f, _g, _h, _j, _k, _l; + var data, action, operId, filter, now, _a, modiCreate, result_1, createInner, multipleCreate, data_2, data_2_1, d, createSingleOper, e_3_1, operatorId, createOper, _b, ids_1, selection, rows, modiUpsert, upsertModis, _c, originData, originId, _d, createOper, updateAttrCount, result; + var e_3, _e, _f, _g, _h, _j, _k, _l; var _this = this; return tslib_1.__generator(this, function (_m) { switch (_m.label) { @@ -766,7 +1022,7 @@ var CascadeStore = /** @class */ (function (_super) { switch (_a) { case 'create': return [3 /*break*/, 1]; } - return [3 /*break*/, 23]; + return [3 /*break*/, 22]; case 1: this.preProcessDataCreated(entity, data); if (!(option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity))) return [3 /*break*/, 3]; @@ -797,7 +1053,7 @@ var CascadeStore = /** @class */ (function (_super) { case 3: result_1 = 0; createInner = function (operation2) { return tslib_1.__awaiter(_this, void 0, void 0, function () { - var _a, e_3; + var _a, e_4; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: @@ -808,7 +1064,7 @@ var CascadeStore = /** @class */ (function (_super) { result_1 = _a + _b.sent(); return [3 /*break*/, 3]; case 2: - e_3 = _b.sent(); + e_4 = _b.sent(); /* 这段代码是处理插入时有重复的行,现在看有问题,等实际需求出现再写 if (e instanceof OakCongruentRowExists) { if (option.allowExists) { @@ -872,7 +1128,7 @@ var CascadeStore = /** @class */ (function (_super) { } } } */ - throw e_3; + throw e_4; case 3: return [2 /*return*/]; } }); @@ -886,11 +1142,11 @@ var CascadeStore = /** @class */ (function (_super) { return [3 /*break*/, 12]; case 5: _m.trys.push([5, 10, 11, 12]); - data_1 = tslib_1.__values(data), data_1_1 = data_1.next(); + data_2 = tslib_1.__values(data), data_2_1 = data_2.next(); _m.label = 6; case 6: - if (!!data_1_1.done) return [3 /*break*/, 9]; - d = data_1_1.value; + if (!!data_2_1.done) return [3 /*break*/, 9]; + d = data_2_1.value; createSingleOper = { id: 'any', action: 'create', @@ -901,18 +1157,18 @@ var CascadeStore = /** @class */ (function (_super) { _m.sent(); _m.label = 8; case 8: - data_1_1 = data_1.next(); + data_2_1 = data_2.next(); return [3 /*break*/, 6]; case 9: return [3 /*break*/, 12]; case 10: - e_2_1 = _m.sent(); - e_2 = { error: e_2_1 }; + e_3_1 = _m.sent(); + e_3 = { error: e_3_1 }; return [3 /*break*/, 12]; case 11: try { - if (data_1_1 && !data_1_1.done && (_e = data_1.return)) _e.call(data_1); + if (data_2_1 && !data_2_1.done && (_e = data_2.return)) _e.call(data_2); } - finally { if (e_2) throw e_2.error; } + finally { if (e_3) throw e_3.error; } return [7 /*endfinally*/]; case 12: return [3 /*break*/, 15]; case 13: return [4 /*yield*/, createInner(operation)]; @@ -927,13 +1183,11 @@ var CascadeStore = /** @class */ (function (_super) { d: data, }); } - if (!(!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity))) return [3 /*break*/, 22]; + if (!(!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity))) return [3 /*break*/, 21]; // 按照框架要求生成Oper和OperEntity这两个内置的对象 (0, assert_1.default)(operId); - return [4 /*yield*/, context.getCurrentUserId(true)]; - case 16: - operatorId = _m.sent(); - if (!operatorId) return [3 /*break*/, 22]; + operatorId = context.getCurrentUserId(true); + if (!operatorId) return [3 /*break*/, 21]; _f = { id: 'dummy', action: 'create' @@ -942,9 +1196,10 @@ var CascadeStore = /** @class */ (function (_super) { id: operId, action: action, data: data, - operatorId: operatorId + operatorId: operatorId, + targetEntity: entity }; - if (!(data instanceof Array)) return [3 /*break*/, 18]; + if (!(data instanceof Array)) return [3 /*break*/, 17]; _h = { id: 'dummy', action: 'create' @@ -957,31 +1212,31 @@ var CascadeStore = /** @class */ (function (_super) { _a = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 1: return [2 /*return*/, (_a.id = _b.sent(), - _a.entity = entity, _a.entityId = ele.id, + _a.entity = entity, _a)]; } }); }); }))]; - case 17: + case 16: _b = (_h.data = _m.sent(), _h); - return [3 /*break*/, 20]; - case 18: + return [3 /*break*/, 19]; + case 17: _j = { id: 'dummy', action: 'create' }; _k = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; - case 19: + case 18: _b = [(_j.data = (_k.id = _m.sent(), - _k.entity = entity, _k.entityId = data.id, + _k.entity = entity, _k), _j)]; - _m.label = 20; - case 20: + _m.label = 19; + case 19: createOper = (_f.data = (_g.operEntity$oper = _b, _g), _f); @@ -989,13 +1244,13 @@ var CascadeStore = /** @class */ (function (_super) { dontCollect: true, dontCreateOper: true, })]; - case 21: + case 20: _m.sent(); - _m.label = 22; - case 22: return [2 /*return*/, result_1]; - case 23: + _m.label = 21; + case 21: return [2 /*return*/, result_1]; + case 22: ids_1 = (0, filter_2.getRelevantIds)(filter); - if (!(ids_1.length === 0)) return [3 /*break*/, 25]; + if (!(ids_1.length === 0)) return [3 /*break*/, 24]; selection = { data: { id: 1, @@ -1007,17 +1262,17 @@ var CascadeStore = /** @class */ (function (_super) { return [4 /*yield*/, this.selectAbjointRowAsync(entity, selection, context, { dontCollect: true, })]; - case 24: + case 23: rows = _m.sent(); ids_1.push.apply(ids_1, tslib_1.__spreadArray([], tslib_1.__read((rows.map(function (ele) { return ele.id; }))), false)); - _m.label = 25; - case 25: + _m.label = 24; + case 24: if (data) { this.preProcessDataUpdated(data); } - if (!(option.modiParentEntity && !['modi', 'modiEntity'].includes(entity))) return [3 /*break*/, 31]; + if (!(option.modiParentEntity && !['modi', 'modiEntity'].includes(entity))) return [3 /*break*/, 30]; modiUpsert = void 0; - if (!(action !== 'remove')) return [3 /*break*/, 27]; + if (!(action !== 'remove')) return [3 /*break*/, 26]; return [4 /*yield*/, this.selectAbjointRowAsync('modi', { data: { id: 1, @@ -1048,7 +1303,7 @@ var CascadeStore = /** @class */ (function (_super) { indexFrom: 0, count: 1, }, context, option)]; - case 26: + case 25: upsertModis = _m.sent(); if (upsertModis.length > 0) { _c = upsertModis[0], originData = _c.data, originId = _c.id; @@ -1063,9 +1318,9 @@ var CascadeStore = /** @class */ (function (_super) { } }; } - _m.label = 27; - case 27: - if (!!modiUpsert) return [3 /*break*/, 29]; + _m.label = 26; + case 26: + if (!!modiUpsert) return [3 /*break*/, 28]; modiUpsert = { id: 'dummy', action: 'create', @@ -1080,7 +1335,7 @@ var CascadeStore = /** @class */ (function (_super) { filter: filter, }, }; - if (!(ids_1.length > 0)) return [3 /*break*/, 29]; + if (!(ids_1.length > 0)) return [3 /*break*/, 28]; _d = modiUpsert.data; _l = { id: 'dummy', @@ -1100,15 +1355,15 @@ var CascadeStore = /** @class */ (function (_super) { } }); }); }))]; - case 28: + case 27: _d.modiEntity$modi = (_l.data = _m.sent(), _l); - _m.label = 29; - case 29: return [4 /*yield*/, this.cascadeUpdateAsync('modi', modiUpsert, context, option)]; - case 30: + _m.label = 28; + case 28: return [4 /*yield*/, this.cascadeUpdateAsync('modi', modiUpsert, context, option)]; + case 29: _m.sent(); return [2 /*return*/, 1]; - case 31: + case 30: createOper = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var createOper_1; var _a, _b, _c; @@ -1126,7 +1381,8 @@ var CascadeStore = /** @class */ (function (_super) { _b = { id: operId, action: action, - data: data + data: data, + targetEntity: entity }; _c = { id: 'dummy', @@ -1140,8 +1396,8 @@ var CascadeStore = /** @class */ (function (_super) { _a = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 1: return [2 /*return*/, (_a.id = _b.sent(), - _a.entity = entity, _a.entityId = ele, + _a.entity = entity, _a)]; } }); @@ -1162,7 +1418,7 @@ var CascadeStore = /** @class */ (function (_super) { } }); }); }; - if (!(action === 'remove')) return [3 /*break*/, 32]; + if (!(action === 'remove')) return [3 /*break*/, 31]; if (!option.dontCollect) { context.opRecords.push({ a: 'r', @@ -1174,10 +1430,10 @@ var CascadeStore = /** @class */ (function (_super) { }, }); } - return [3 /*break*/, 36]; - case 32: + return [3 /*break*/, 35]; + case 31: updateAttrCount = Object.keys(data).length; - if (!(updateAttrCount > 0)) return [3 /*break*/, 33]; + if (!(updateAttrCount > 0)) return [3 /*break*/, 32]; // 优化一下,如果不更新任何属性,则不实际执行 Object.assign(data, { $$updateAt$$: now, @@ -1194,21 +1450,21 @@ var CascadeStore = /** @class */ (function (_super) { }, }); } - return [3 /*break*/, 36]; - case 33: - if (!(action !== 'update')) return [3 /*break*/, 35]; + return [3 /*break*/, 35]; + case 32: + if (!(action !== 'update')) return [3 /*break*/, 34]; // 如果不是update动作而是用户自定义的动作,这里还是要记录oper return [4 /*yield*/, createOper()]; - case 34: + case 33: // 如果不是update动作而是用户自定义的动作,这里还是要记录oper _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: + case 34: return [2 /*return*/, 0]; + case 35: return [4 /*yield*/, this.updateAbjointRowAsync(entity, operation, context, option)]; + case 36: result = _m.sent(); return [4 /*yield*/, createOper()]; - case 38: + case 37: _m.sent(); return [2 /*return*/, result]; } @@ -1216,7 +1472,7 @@ var CascadeStore = /** @class */ (function (_super) { }); }; CascadeStore.prototype.doUpdateSingleRow = function (entity, operation, context, option) { - var e_4, _a; + var e_5, _a; var _this = this; var data = operation.data, action = operation.action, operId = operation.id, filter = operation.filter; var now = Date.now(); @@ -1239,8 +1495,8 @@ var CascadeStore = /** @class */ (function (_super) { } else { try { - for (var data_2 = tslib_1.__values(data), data_2_1 = data_2.next(); !data_2_1.done; data_2_1 = data_2.next()) { - var d = data_2_1.value; + for (var data_3 = tslib_1.__values(data), data_3_1 = data_3.next(); !data_3_1.done; data_3_1 = data_3.next()) { + var d = data_3_1.value; var createSingleOper = { id: 'any', action: 'create', @@ -1249,12 +1505,12 @@ var CascadeStore = /** @class */ (function (_super) { createInner(createSingleOper); } } - catch (e_4_1) { e_4 = { error: e_4_1 }; } + catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { - if (data_2_1 && !data_2_1.done && (_a = data_2.return)) _a.call(data_2); + if (data_3_1 && !data_3_1.done && (_a = data_3.return)) _a.call(data_3); } - finally { if (e_4) throw e_4.error; } + finally { if (e_5) throw e_5.error; } } } } @@ -1284,8 +1540,8 @@ var CascadeStore = /** @class */ (function (_super) { } }; CascadeStore.prototype.cascadeUpdate = function (entity, operation, context, option) { - var e_5, _a, e_6, _b, e_7, _c; - (0, selection_1.reinforceOperation)(this.getSchema(), entity, operation); + var e_6, _a, e_7, _b, e_8, _c; + this.reinforceOperation(entity, operation); var action = operation.action, data = operation.data, filter = operation.filter, id = operation.id; var opData; var wholeBeforeFns = []; @@ -1294,20 +1550,20 @@ var CascadeStore = /** @class */ (function (_super) { if (['create', 'create-l'].includes(action) && data instanceof Array) { opData = []; try { - for (var data_3 = tslib_1.__values(data), data_3_1 = data_3.next(); !data_3_1.done; data_3_1 = data_3.next()) { - var d = data_3_1.value; + for (var data_4 = tslib_1.__values(data), data_4_1 = data_4.next(); !data_4_1.done; data_4_1 = data_4.next()) { + var d = data_4_1.value; var _d = this.destructCascadeUpdate(entity, action, d, context, option, this.cascadeUpdate), od = _d.data, beforeFns = _d.beforeFns, afterFns = _d.afterFns; opData.push(od); wholeBeforeFns.push.apply(wholeBeforeFns, tslib_1.__spreadArray([], tslib_1.__read(beforeFns), false)); wholeAfterFns.push.apply(wholeAfterFns, tslib_1.__spreadArray([], tslib_1.__read(afterFns), false)); } } - catch (e_5_1) { e_5 = { error: e_5_1 }; } + catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { - if (data_3_1 && !data_3_1.done && (_a = data_3.return)) _a.call(data_3); + if (data_4_1 && !data_4_1.done && (_a = data_4.return)) _a.call(data_4); } - finally { if (e_5) throw e_5.error; } + finally { if (e_6) throw e_6.error; } } } else { @@ -1325,12 +1581,12 @@ var CascadeStore = /** @class */ (function (_super) { before_1(); } } - catch (e_6_1) { e_6 = { error: e_6_1 }; } + catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (wholeBeforeFns_1_1 && !wholeBeforeFns_1_1.done && (_b = wholeBeforeFns_1.return)) _b.call(wholeBeforeFns_1); } - finally { if (e_6) throw e_6.error; } + finally { if (e_7) throw e_7.error; } } var count = this.doUpdateSingleRow(entity, operation2, context, option); try { @@ -1339,12 +1595,12 @@ var CascadeStore = /** @class */ (function (_super) { after_1(); } } - catch (e_7_1) { e_7 = { error: e_7_1 }; } + catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (wholeAfterFns_1_1 && !wholeAfterFns_1_1.done && (_c = wholeAfterFns_1.return)) _c.call(wholeAfterFns_1); } - finally { if (e_7) throw e_7.error; } + finally { if (e_8) throw e_8.error; } } return result; }; @@ -1357,12 +1613,12 @@ var CascadeStore = /** @class */ (function (_super) { */ CascadeStore.prototype.cascadeUpdateAsync = function (entity, operation, context, option) { return tslib_1.__awaiter(this, void 0, void 0, function () { - var action, data, filter, id, opData, wholeBeforeFns, wholeAfterFns, result, data_4, data_4_1, d, _a, od, beforeFns, afterFns, _b, od, beforeFns, afterFns, operation2, wholeBeforeFns_2, wholeBeforeFns_2_1, before_2, e_8_1, count, wholeAfterFns_2, wholeAfterFns_2_1, after_2, e_9_1; - var e_10, _c, e_8, _d, _e, _f, e_9, _g; + var action, data, filter, id, opData, wholeBeforeFns, wholeAfterFns, result, data_5, data_5_1, d, _a, od, beforeFns, afterFns, _b, od, beforeFns, afterFns, operation2, wholeBeforeFns_2, wholeBeforeFns_2_1, before_2, e_9_1, count, wholeAfterFns_2, wholeAfterFns_2_1, after_2, e_10_1; + var e_11, _c, e_9, _d, _e, _f, e_10, _g; return tslib_1.__generator(this, function (_h) { switch (_h.label) { case 0: - (0, selection_1.reinforceOperation)(this.getSchema(), entity, operation); + this.reinforceOperation(entity, operation); action = operation.action, data = operation.data, filter = operation.filter, id = operation.id; wholeBeforeFns = []; wholeAfterFns = []; @@ -1370,20 +1626,20 @@ var CascadeStore = /** @class */ (function (_super) { if (['create', 'create-l'].includes(action) && data instanceof Array) { opData = []; try { - for (data_4 = tslib_1.__values(data), data_4_1 = data_4.next(); !data_4_1.done; data_4_1 = data_4.next()) { - d = data_4_1.value; + for (data_5 = tslib_1.__values(data), data_5_1 = data_5.next(); !data_5_1.done; data_5_1 = data_5.next()) { + d = data_5_1.value; _a = this.destructCascadeUpdate(entity, action, d, context, option, this.cascadeUpdateAsync), od = _a.data, beforeFns = _a.beforeFns, afterFns = _a.afterFns; opData.push(od); wholeBeforeFns.push.apply(wholeBeforeFns, tslib_1.__spreadArray([], tslib_1.__read(beforeFns), false)); wholeAfterFns.push.apply(wholeAfterFns, tslib_1.__spreadArray([], tslib_1.__read(afterFns), false)); } } - catch (e_10_1) { e_10 = { error: e_10_1 }; } + catch (e_11_1) { e_11 = { error: e_11_1 }; } finally { try { - if (data_4_1 && !data_4_1.done && (_c = data_4.return)) _c.call(data_4); + if (data_5_1 && !data_5_1.done && (_c = data_5.return)) _c.call(data_5); } - finally { if (e_10) throw e_10.error; } + finally { if (e_11) throw e_11.error; } } } else { @@ -1412,14 +1668,14 @@ var CascadeStore = /** @class */ (function (_super) { return [3 /*break*/, 2]; case 5: return [3 /*break*/, 8]; case 6: - e_8_1 = _h.sent(); - e_8 = { error: e_8_1 }; + e_9_1 = _h.sent(); + e_9 = { error: e_9_1 }; return [3 /*break*/, 8]; case 7: try { if (wholeBeforeFns_2_1 && !wholeBeforeFns_2_1.done && (_d = wholeBeforeFns_2.return)) _d.call(wholeBeforeFns_2); } - finally { if (e_8) throw e_8.error; } + finally { if (e_9) throw e_9.error; } return [7 /*endfinally*/]; case 8: return [4 /*yield*/, this.doUpdateSingleRowAsync(entity, operation2, context, option)]; case 9: @@ -1446,14 +1702,14 @@ var CascadeStore = /** @class */ (function (_super) { return [3 /*break*/, 11]; case 14: return [3 /*break*/, 17]; case 15: - e_9_1 = _h.sent(); - e_9 = { error: e_9_1 }; + e_10_1 = _h.sent(); + e_10 = { error: e_10_1 }; return [3 /*break*/, 17]; case 16: try { if (wholeAfterFns_2_1 && !wholeAfterFns_2_1.done && (_g = wholeAfterFns_2.return)) _g.call(wholeAfterFns_2); } - finally { if (e_9) throw e_9.error; } + finally { if (e_10) throw e_10.error; } return [7 /*endfinally*/]; case 17: return [2 /*return*/, result]; } @@ -1461,7 +1717,7 @@ var CascadeStore = /** @class */ (function (_super) { }); }; CascadeStore.prototype.cascadeSelect = function (entity, selection, context, option) { - (0, selection_1.reinforceSelection)(this.getSchema(), entity, selection); + 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, { @@ -1544,11 +1800,11 @@ var CascadeStore = /** @class */ (function (_super) { var id = row.id; if (!entityBranch_1[id]) { Object.assign(entityBranch_1, (_a = {}, - _a[id] = row, + _a[id] = (0, lodash_1.cloneDeep)(row), _a)); } else { - Object.assign(entityBranch_1[id], row); + Object.assign(entityBranch_1[id], (0, lodash_1.cloneDeep)(row)); } } }); @@ -1568,7 +1824,7 @@ var CascadeStore = /** @class */ (function (_super) { if (row) { var id = row.id; Object.assign(entityBranch, (_a = {}, - _a[id] = row, + _a[id] = (0, lodash_1.cloneDeep)(row), _a)); } }); @@ -1583,7 +1839,7 @@ var CascadeStore = /** @class */ (function (_super) { return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: - (0, selection_1.reinforceSelection)(this.getSchema(), entity, selection); + this.reinforceSelection(entity, selection); data = selection.data, filter = selection.filter, indexFrom = selection.indexFrom, count = selection.count, sorter = selection.sorter; _a = this.destructCascadeSelect(entity, data, context, this.cascadeSelectAsync, this.aggregateAsync, option), projection = _a.projection, cascadeSelectionFns = _a.cascadeSelectionFns; return [4 /*yield*/, this.selectAbjointRowAsync(entity, { @@ -1601,7 +1857,7 @@ var CascadeStore = /** @class */ (function (_super) { if (!(cascadeSelectionFns.length > 0)) return [3 /*break*/, 3]; ruException_2 = []; return [4 /*yield*/, Promise.all(cascadeSelectionFns.map(function (ele) { return tslib_1.__awaiter(_this, void 0, void 0, function () { - var e_11, rows_2; + var e_12, rows_2; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: @@ -1611,13 +1867,13 @@ var CascadeStore = /** @class */ (function (_super) { _a.sent(); return [3 /*break*/, 3]; case 2: - e_11 = _a.sent(); - if (e_11 instanceof types_1.OakRowUnexistedException) { - rows_2 = e_11.getRows(); + e_12 = _a.sent(); + if (e_12 instanceof types_1.OakRowUnexistedException) { + rows_2 = e_12.getRows(); ruException_2.push.apply(ruException_2, tslib_1.__spreadArray([], tslib_1.__read(rows_2), false)); } else { - throw e_11; + throw e_12; } return [3 /*break*/, 3]; case 3: return [2 /*return*/]; diff --git a/lib/store/SyncRowStore.d.ts b/lib/store/SyncRowStore.d.ts index 666c50a..683d598 100644 --- a/lib/store/SyncRowStore.d.ts +++ b/lib/store/SyncRowStore.d.ts @@ -1,6 +1,6 @@ import { EntityDict, RowStore, OperateOption, OperationResult, SelectOption, TxnOption, Context, AggregationResult } from "../types"; export declare abstract class SyncContext implements Context { - private rowStore; + rowStore: SyncRowStore; private uuid?; constructor(rowStore: SyncRowStore>); abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined; diff --git a/lib/store/TriggerExecutor.js b/lib/store/TriggerExecutor.js index c6a7e8f..2ad37ef 100644 --- a/lib/store/TriggerExecutor.js +++ b/lib/store/TriggerExecutor.js @@ -33,7 +33,7 @@ var TriggerExecutor = /** @class */ (function () { TriggerExecutor.prototype.registerChecker = function (checker) { var entity = checker.entity, action = checker.action, type = checker.type, conditionalFilter = checker.conditionalFilter; var triggerName = "".concat(String(entity)).concat(action, "\u6743\u9650\u68C0\u67E5-").concat(this.counter++); - var _a = (0, checker_1.translateCheckerInAsyncContext)(checker, true), 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 = { checkerType: type, diff --git a/lib/store/checker.d.ts b/lib/store/checker.d.ts index 71791d7..7aa7e0c 100644 --- a/lib/store/checker.d.ts +++ b/lib/store/checker.d.ts @@ -8,7 +8,7 @@ import { SyncContext } from './SyncRowStore'; * @param silent 如果silent,则row和relation类型的checker只会把限制条件加到查询上,而不报错(除掉create动作) * @returns */ -export declare function translateCheckerInAsyncContext>(checker: Checker, silent?: boolean): { +export declare function translateCheckerInAsyncContext>(checker: Checker): { fn: Trigger['fn']; when: 'before' | 'after'; }; diff --git a/lib/store/checker.js b/lib/store/checker.js index 957b8ff..550fe04 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -17,7 +17,7 @@ var uuid_1 = require("../utils/uuid"); * @param silent 如果silent,则row和relation类型的checker只会把限制条件加到查询上,而不报错(除掉create动作) * @returns */ -function translateCheckerInAsyncContext(checker, silent) { +function translateCheckerInAsyncContext(checker) { var _this = this; var entity = checker.entity, type = checker.type; var when = 'before'; // 现在create的relation改成提前的expression检查了,原先是先插入再后检查,性能不行,而且select也需要实现前检查 @@ -65,7 +65,7 @@ function translateCheckerInAsyncContext(checker, silent) { _c.label = 3; case 3: filter2 = _b; - if (!silent) return [3 /*break*/, 4]; + if (!['select', 'count', 'stat'].includes(action)) return [3 /*break*/, 4]; operation.filter = (0, filter_1.addFilterSegment)(operationFilter || {}, filter2); return [2 /*return*/, 0]; case 4: return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter || {}, true)]; @@ -135,7 +135,7 @@ function translateCheckerInAsyncContext(checker, silent) { console.warn("".concat(entity, "\u5BF9\u8C61\u7684create\u7C7B\u578B\u7684checker\u4E2D\uFF0C\u5B58\u5728\u65E0\u6CD5\u8F6C\u6362\u4E3A\u8868\u8FBE\u5F0F\u5F62\u5F0F\u7684\u60C5\u51B5\uFF0C\u8BF7\u5C3D\u91CF\u4F7F\u7528authDef\u683C\u5F0F\u5B9A\u4E49\u8FD9\u7C7Bchecker")); return [2 /*return*/, 0]; } - if (silent) { + if (['select', 'count', 'stat'].includes(action)) { operation.filter = (0, filter_1.addFilterSegment)(filter || {}, result); return [2 /*return*/, 0]; } @@ -409,7 +409,7 @@ function translateCascadeRelationFilterMaker(schema, lch, entity2, pathPrefix) { counters.push.apply(counters, tslib_1.__spreadArray([], tslib_1.__read(ca2), false)); } if (filter.$or) { - var countersOr = filter.$and.map(function (ele) { return translateCreateFilterMaker(entity, ele, userId); }); + var countersOr = filter.$or.map(function (ele) { return translateCreateFilterMaker(entity, ele, userId); }); // or也只要有一个满足就行(不能否定) var co2 = countersOr.filter(function (ele) { return !(ele instanceof Exception_1.OakUserUnpermittedException); }); counters.push.apply(counters, tslib_1.__spreadArray([], tslib_1.__read(co2), false)); diff --git a/lib/store/selection.d.ts b/lib/store/selection.d.ts deleted file mode 100644 index 7999cb4..0000000 --- a/lib/store/selection.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { StorageSchema } from '../types'; -import { EntityDict } from '../types/Entity'; -declare type SelectionRewriter = (schema: StorageSchema, entity: keyof ED, selection: ED[keyof ED]['Selection']) => void; -export declare function registerSelectionRewriter(rewriter: SelectionRewriter): void; -declare type OperationRewriter = (schema: StorageSchema, entity: keyof ED, operate: ED[keyof ED]['Operation']) => void; -export declare function registerOperationRewriter(rewriter: OperationRewriter): void; -/** - * 对selection进行一些完善,避免编程人员的疏漏 - * @param selection - */ -export declare function reinforceSelection(schema: StorageSchema, entity: keyof ED, selection: ED[keyof ED]['Selection']): void; -/** - * 对operation进行一些完善,作为operation算子的注入点 - * @param schema - * @param entity - * @param selection - */ -export declare function reinforceOperation(schema: StorageSchema, entity: keyof ED, operation: ED[keyof ED]['Operation']): void; -export {}; diff --git a/lib/store/selection.js b/lib/store/selection.js deleted file mode 100644 index cf9cc30..0000000 --- a/lib/store/selection.js +++ /dev/null @@ -1,265 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.reinforceOperation = exports.reinforceSelection = exports.registerOperationRewriter = exports.registerSelectionRewriter = void 0; -var tslib_1 = require("tslib"); -var assert_1 = tslib_1.__importDefault(require("assert")); -var types_1 = require("../types"); -var Demand_1 = require("../types/Demand"); -var filter_1 = require("./filter"); -var relation_1 = require("./relation"); -var SelectionRewriters = []; -function registerSelectionRewriter(rewriter) { - SelectionRewriters.push(rewriter); -} -exports.registerSelectionRewriter = registerSelectionRewriter; -function getSelectionRewriters() { - return SelectionRewriters; -} -var OperationRewriters = []; -function registerOperationRewriter(rewriter) { - OperationRewriters.push(rewriter); -} -exports.registerOperationRewriter = registerOperationRewriter; -function getOperationRewriters() { - return OperationRewriters; -} -/** - * 对selection进行一些完善,避免编程人员的疏漏 - * @param selection - */ -function reinforceSelection(schema, entity, selection) { - var filter = selection.filter, data = selection.data, sorter = selection.sorter; - var checkNode = function (projectionNode, attrs) { - attrs.forEach(function (attr) { - var _a; - if (!projectionNode.hasOwnProperty(attr)) { - Object.assign(projectionNode, (_a = {}, - _a[attr] = 1, - _a)); - } - }); - }; - var relevantIds = []; - if (filter) { - var toBeAssignNode_1 = {}; // 用来记录在表达式中涉及到的结点 - // filter当中所关联到的属性必须在projection中 - var filterNodeDict_1 = {}; - var checkFilterNode_1 = function (entity2, filterNode, projectionNode) { - var _a, e_1, _b, _c, _d, _e, _f; - var necessaryAttrs = ['id']; - for (var attr in filterNode) { - if (attr === '#id') { - (0, assert_1.default)(!filterNodeDict_1[filterNode[attr]], "projection\u4E2D\u7ED3\u70B9\u7684id\u6709\u91CD\u590D, ".concat(filterNode[attr])); - Object.assign(filterNodeDict_1, (_a = {}, - _a[filterNode[attr]] = projectionNode, - _a)); - if (toBeAssignNode_1[filterNode[attr]]) { - checkNode(projectionNode, toBeAssignNode_1[filterNode[attr]]); - } - } - else if (['$and', '$or'].includes(attr)) { - try { - for (var _g = (e_1 = void 0, tslib_1.__values(filterNode[attr])), _h = _g.next(); !_h.done; _h = _g.next()) { - var node = _h.value; - checkFilterNode_1(entity2, node, projectionNode); - } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (_h && !_h.done && (_b = _g.return)) _b.call(_g); - } - finally { if (e_1) throw e_1.error; } - } - } - else if (attr === '$not') { - checkFilterNode_1(entity2, filterNode[attr], projectionNode); - } - else if (attr === '$text') { - // 全文检索首先要有fulltext索引,其次要把fulltext的相关属性加到projection里 - var indexes = schema[entity2].indexes; - var fulltextIndex = indexes.find(function (ele) { return ele.config && ele.config.type === 'fulltext'; }); - var attributes = fulltextIndex.attributes; - necessaryAttrs.push.apply(necessaryAttrs, tslib_1.__spreadArray([], tslib_1.__read((attributes.map(function (ele) { return ele.name; }))), false)); - } - else { - if (attr.toLowerCase().startsWith(Demand_1.EXPRESSION_PREFIX)) { - var exprResult = (0, types_1.getAttrRefInExpression)(filterNode[attr]); - for (var nodeName in exprResult) { - if (nodeName === '#current') { - checkNode(projectionNode, exprResult[nodeName]); - } - else if (filterNodeDict_1[nodeName]) { - checkNode(filterNodeDict_1[nodeName], exprResult[nodeName]); - } - else { - if (toBeAssignNode_1[nodeName]) { - (_c = toBeAssignNode_1[nodeName]).push.apply(_c, tslib_1.__spreadArray([], tslib_1.__read(exprResult[nodeName]), false)); - } - else { - Object.assign(toBeAssignNode_1, (_d = {}, - _d[nodeName] = exprResult[nodeName], - _d)); - } - } - } - } - else { - var rel = (0, relation_1.judgeRelation)(schema, entity2, attr); - if (rel === 1) { - necessaryAttrs.push(attr); - } - else if (rel === 2) { - // entity/entityId反指 - necessaryAttrs.push('entity', 'entityId'); - if (!projectionNode[attr]) { - Object.assign(projectionNode, (_e = {}, - _e[attr] = { - id: 1, - }, - _e)); - } - checkFilterNode_1(attr, filterNode[attr], projectionNode[attr]); - } - else if (typeof rel === 'string') { - necessaryAttrs.push("".concat(attr, "Id")); - if (!projectionNode[attr]) { - Object.assign(projectionNode, (_f = {}, - _f[attr] = { - id: 1, - }, - _f)); - } - checkFilterNode_1(rel, filterNode[attr], projectionNode[attr]); - } - else if (rel instanceof Array) { - // 现在filter中还不支持一对多的语义,先放着吧 - } - } - } - checkNode(projectionNode, necessaryAttrs); - } - }; - checkFilterNode_1(entity, filter, data); - relevantIds = (0, filter_1.getRelevantIds)(filter); - } - // sorter感觉现在取不取影响不大,前端的list直接获取返回的ids了,先不管之 - if (sorter) { - } - var toBeAssignNode2 = {}; // 用来记录在表达式中涉及到的结点 - var projectionNodeDict = {}; - var checkProjectionNode = function (entity2, projectionNode) { - 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(Demand_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)); - } - } - } - } - else { - var rel = (0, relation_1.judgeRelation)(schema, 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]]); - } - else { - checkNode(data_1, ['entity', 'entityId']); - } - reinforceSelection(schema, rel[0], projectionNode[attr]); - } - } - } - checkNode(projectionNode, necessaryAttrs); - } - // 如果对象中指向一对多的Modi,此时加上指向Modi的projection - if (schema[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', - }, - } - }); - } - }; - checkProjectionNode(entity, data); - if (!sorter && relevantIds.length === 0) { - // 如果没有sorter,就给予一个按createAt逆序的sorter - Object.assign(selection, { - sorter: [ - { - $attr: { - $$createAt$$: 1, - }, - $direction: 'desc', - } - ] - }); - Object.assign(data, { - $$createAt$$: 1, - }); - } - SelectionRewriters.forEach(function (ele) { return ele(schema, entity, selection); }); -} -exports.reinforceSelection = reinforceSelection; -/** - * 对operation进行一些完善,作为operation算子的注入点 - * @param schema - * @param entity - * @param selection - */ -function reinforceOperation(schema, entity, operation) { - OperationRewriters.forEach(function (ele) { return ele(schema, entity, operation); }); -} -exports.reinforceOperation = reinforceOperation; diff --git a/lib/timers/oper.d.ts b/lib/timers/oper.d.ts new file mode 100644 index 0000000..9eebe95 --- /dev/null +++ b/lib/timers/oper.d.ts @@ -0,0 +1,18 @@ +import { EntityDict } from '../types/Entity'; +import { EntityDict as BaseEntityDict } from '../base-app-domain'; +import { AsyncContext } from '../store/AsyncRowStore'; +export declare type VaccumOperOption = { + aliveLine: number; + excludeOpers?: { + [T in keyof ED]?: ED[T]['Action'][]; + }; + backupDir?: string; + zip?: boolean; +}; +/** + * 将一定日期之前的oper对象清空 + * @param option + * @param context + * @returns + */ +export declare function vaccumOper>(option: VaccumOperOption, context: Cxt): Promise; diff --git a/lib/timers/oper.js b/lib/timers/oper.js new file mode 100644 index 0000000..b0fbbdf --- /dev/null +++ b/lib/timers/oper.js @@ -0,0 +1,60 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.vaccumOper = void 0; +var tslib_1 = require("tslib"); +var vaccum_1 = require("./vaccum"); +var filter_1 = require("../store/filter"); +/** + * 将一定日期之前的oper对象清空 + * @param option + * @param context + * @returns + */ +function vaccumOper(option, context) { + return tslib_1.__awaiter(this, void 0, void 0, function () { + var aliveLine, excludeOpers, rest, operFilter, notFilters, key; + return tslib_1.__generator(this, function (_a) { + aliveLine = option.aliveLine, excludeOpers = option.excludeOpers, rest = tslib_1.__rest(option, ["aliveLine", "excludeOpers"]); + operFilter = {}; + if (excludeOpers) { + notFilters = []; + for (key in excludeOpers) { + if (excludeOpers[key].length > 0) { + notFilters.push({ + targetEntity: key, + action: { + $in: excludeOpers[key], + } + }); + } + else { + notFilters.push({ + targetEntity: key, + }); + } + } + if (notFilters.length > 0) { + operFilter.$not = { + $or: notFilters, + }; + } + } + return [2 /*return*/, (0, vaccum_1.vaccumEntities)(tslib_1.__assign({ entities: [{ + entity: 'operEntity', + aliveLine: aliveLine + 10000, + filter: { + oper: (0, filter_1.combineFilters)([operFilter, { + $$createAt$$: { + $lt: aliveLine, + } + }]), + }, + }, { + entity: 'oper', + aliveLine: aliveLine, + filter: operFilter, + }] }, rest), context)]; + }); + }); +} +exports.vaccumOper = vaccumOper; diff --git a/lib/timers/vaccum.d.ts b/lib/timers/vaccum.d.ts new file mode 100644 index 0000000..bd3ef80 --- /dev/null +++ b/lib/timers/vaccum.d.ts @@ -0,0 +1,20 @@ +import { EntityDict } from '../types/Entity'; +import { EntityDict as BaseEntityDict } from '../base-app-domain'; +import { AsyncContext } from '../store/AsyncRowStore'; +declare type VaccumOptionEntity = { + entity: T; + filter?: ED[T]['Selection']['filter']; + aliveLine: number; +}; +declare type VaccumOption = { + entities: Array>; + backupDir?: string; + zip?: boolean; +}; +/** + * 删除数据库中的部分数据,减少体积 + * 一般只删除日志类数据 + * @param option + */ +export declare function vaccumEntities>(option: VaccumOption, context: Cxt): Promise; +export {}; diff --git a/lib/timers/vaccum.js b/lib/timers/vaccum.js new file mode 100644 index 0000000..8d68731 --- /dev/null +++ b/lib/timers/vaccum.js @@ -0,0 +1,176 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.vaccumEntities = void 0; +var tslib_1 = require("tslib"); +var dayjs_1 = tslib_1.__importDefault(require("dayjs")); +var fs_1 = require("fs"); +var filter_1 = require("../store/filter"); +var node_zlib_1 = require("node:zlib"); +var stream_1 = require("stream"); +var uuid_1 = require("../utils/uuid"); +/** + * 删除数据库中的部分数据,减少体积 + * 一般只删除日志类数据 + * @param option + */ +function vaccumEntities(option, context) { + return tslib_1.__awaiter(this, void 0, void 0, function () { + var entities, backupDir, _loop_1, entities_1, entities_1_1, ele, e_1_1; + var e_1, _a; + var _this = this; + return tslib_1.__generator(this, function (_b) { + switch (_b.label) { + case 0: + entities = option.entities, backupDir = option.backupDir; + _loop_1 = function (ele) { + var entity, filter, aliveLine, filter2, zip, now, backFile, fd_1, attributes_1, projection_1, attr, count_1, appendData_1, gzip_1, source_1, destination_1, _c, _d, _e; + var _f, _g; + return tslib_1.__generator(this, function (_h) { + switch (_h.label) { + case 0: + entity = ele.entity, filter = ele.filter, aliveLine = ele.aliveLine; + filter2 = { + $$createAt$$: { + $lt: aliveLine, + }, + }; + if (filter) { + filter2 = (0, filter_1.combineFilters)([filter2, filter]); + } + if (!backupDir) return [3 /*break*/, 4]; + zip = option.zip; + now = (0, dayjs_1.default)(); + backFile = "".concat(backupDir, "/").concat(entity, "-").concat(now.format('YYYY-MM-DD HH:mm:ss'), ".csv"); + if ((0, fs_1.existsSync)(backFile)) { + (0, fs_1.rmSync)(backFile); + } + fd_1 = (0, fs_1.openSync)(backFile, 'a'); + attributes_1 = ['id', '$$createAt$$', '$$updateAt$$', '$$deleteAt$$']; + projection_1 = { + id: 1, + $$createAt$$: 1, + $$updateAt$$: 1, + $$deleteAt$$: 1, + }; + for (attr in context.getSchema()[entity].attributes) { + Object.assign(projection_1, (_f = {}, + _f[attr] = 1, + _f)); + attributes_1.push(attr); + } + (0, fs_1.appendFileSync)(fd_1, attributes_1.join(',')); + (0, fs_1.appendFileSync)(fd_1, '\n'); + count_1 = 0; + appendData_1 = function (minCreateAt) { return tslib_1.__awaiter(_this, void 0, void 0, function () { + var filter3, rows, csvTxt, maxCreateAt; + return tslib_1.__generator(this, function (_a) { + switch (_a.label) { + case 0: + filter3 = (0, filter_1.combineFilters)([filter2, { + $$createAt$$: { + $gt: minCreateAt, + }, + }]); + return [4 /*yield*/, context.select(entity, { + data: projection_1, + filter: filter3, + sorter: [{ + $attr: { + $$createAt$$: 1, + }, + $direction: 'asc' + }], + indexFrom: 0, + count: 1000, + }, { includedDeleted: true })]; + case 1: + rows = _a.sent(); + csvTxt = rows.map(function (row) { return attributes_1.map(function (attr) { return JSON.stringify(row[attr]); }).join(','); }).join('\n'); + (0, fs_1.appendFileSync)(fd_1, csvTxt); + (0, fs_1.appendFileSync)(fd_1, '\n'); + count_1 += rows.length; + if (rows.length === 1000) { + maxCreateAt = rows[999].$$createAt$$; + return [2 /*return*/, appendData_1(maxCreateAt)]; + } + return [2 /*return*/]; + } + }); + }); }; + return [4 /*yield*/, appendData_1(0)]; + case 1: + _h.sent(); + (0, fs_1.closeSync)(fd_1); + console.log("\u5907\u4EFD".concat(entity, "\u5BF9\u8C61\u5B8C\u6BD5\uFF0C\u5171\u5907\u4EFD\u4E86").concat(count_1, "\u884C\u6570\u636E")); + if (!(count_1 === 0)) return [3 /*break*/, 2]; + (0, fs_1.rmSync)(backFile); + return [3 /*break*/, 4]; + case 2: + if (!zip) return [3 /*break*/, 4]; + gzip_1 = (0, node_zlib_1.createGzip)(); + source_1 = (0, fs_1.createReadStream)(backFile); + destination_1 = (0, fs_1.createWriteStream)("".concat(backFile, ".zip")); + return [4 /*yield*/, new Promise(function (resolve, reject) { + (0, stream_1.pipeline)(source_1, gzip_1, destination_1, function (err) { + if (err) { + reject(err); + } + else { + resolve(undefined); + } + }); + })]; + case 3: + _h.sent(); + _h.label = 4; + case 4: + _d = (_c = context).operate; + _e = [entity]; + _g = {}; + return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; + case 5: + // 将对应的数据删除 + return [4 /*yield*/, _d.apply(_c, _e.concat([(_g.id = _h.sent(), + _g.action = 'remove', + _g.data = {}, + _g.filter = filter2, + _g), { deletePhysically: true }]))]; + case 6: + // 将对应的数据删除 + _h.sent(); + return [2 /*return*/]; + } + }); + }; + _b.label = 1; + case 1: + _b.trys.push([1, 6, 7, 8]); + entities_1 = tslib_1.__values(entities), entities_1_1 = entities_1.next(); + _b.label = 2; + case 2: + if (!!entities_1_1.done) return [3 /*break*/, 5]; + ele = entities_1_1.value; + return [5 /*yield**/, _loop_1(ele)]; + case 3: + _b.sent(); + _b.label = 4; + case 4: + entities_1_1 = entities_1.next(); + return [3 /*break*/, 2]; + case 5: return [3 /*break*/, 8]; + case 6: + e_1_1 = _b.sent(); + e_1 = { error: e_1_1 }; + return [3 /*break*/, 8]; + case 7: + 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 [7 /*endfinally*/]; + case 8: return [2 /*return*/]; + } + }); + }); +} +exports.vaccumEntities = vaccumEntities; diff --git a/lib/types/Connector.d.ts b/lib/types/Connector.d.ts index 41e5786..1606e5f 100644 --- a/lib/types/Connector.d.ts +++ b/lib/types/Connector.d.ts @@ -16,10 +16,10 @@ export declare abstract class Connector; - abstract serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): { + abstract serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): Promise<{ body: any; headers?: Record; - }; + }>; abstract serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record; diff --git a/lib/types/Entity.d.ts b/lib/types/Entity.d.ts index 2baa18a..99255df 100644 --- a/lib/types/Entity.d.ts +++ b/lib/types/Entity.d.ts @@ -37,6 +37,7 @@ export declare type OperateOption = { allowExists?: boolean; modiParentId?: string; modiParentEntity?: string; + deletePhysically?: boolean; dummy?: 1; }; export declare type FormUpdateData = Partial<{ diff --git a/lib/types/RowStore.d.ts b/lib/types/RowStore.d.ts index 8c632f0..03940e1 100644 --- a/lib/types/RowStore.d.ts +++ b/lib/types/RowStore.d.ts @@ -3,9 +3,13 @@ import { StorageSchema } from './Storage'; export declare type TxnOption = { isolationLevel: 'repeatable read' | 'serializable'; }; +export declare type SelectionRewriter = (schema: StorageSchema, entity: keyof ED, selection: ED[keyof ED]['Selection']) => void; +export declare type OperationRewriter = (schema: StorageSchema, entity: keyof ED, operate: ED[keyof ED]['Operation']) => void; export declare abstract class RowStore { protected storageSchema: StorageSchema; constructor(storageSchema: StorageSchema); + abstract registerOperationRewriter(rewriter: OperationRewriter): void; + abstract registerSelectionRewriter(rewriter: SelectionRewriter): void; getSchema(): StorageSchema; mergeOperationResult(result: OperationResult, toBeMerged: OperationResult): void; mergeMultipleResults(toBeMerged: OperationResult[]): OperationResult; diff --git a/lib/utils/SimpleConnector.d.ts b/lib/utils/SimpleConnector.d.ts index 6d41251..197a2be 100644 --- a/lib/utils/SimpleConnector.d.ts +++ b/lib/utils/SimpleConnector.d.ts @@ -24,10 +24,10 @@ export declare class SimpleConnector; - serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): { + serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): Promise<{ body: any; headers?: Record | undefined; - }; + }>; serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record | undefined; diff --git a/lib/utils/SimpleConnector.js b/lib/utils/SimpleConnector.js index b5333cd..b26434e 100644 --- a/lib/utils/SimpleConnector.js +++ b/lib/utils/SimpleConnector.js @@ -107,20 +107,30 @@ var SimpleConnector = /** @class */ (function (_super) { }); }; SimpleConnector.prototype.serializeResult = function (result, context, headers, body) { - if (result instanceof stream_1.Stream || result instanceof Buffer) { - return { - body: result, - }; - } - return { - body: { - result: result, - opRecords: context.opRecords, - }, - headers: { - 'oak-message': context.getMessage(), - }, - }; + return tslib_1.__awaiter(this, void 0, void 0, function () { + return tslib_1.__generator(this, function (_a) { + switch (_a.label) { + case 0: + if (result instanceof stream_1.Stream || result instanceof Buffer) { + return [2 /*return*/, { + body: result, + }]; + } + return [4 /*yield*/, context.refineOpRecords()]; + case 1: + _a.sent(); + return [2 /*return*/, { + body: { + result: result, + opRecords: context.opRecords, + }, + headers: { + 'oak-message': context.getMessage(), + }, + }]; + } + }); + }); }; SimpleConnector.prototype.serializeException = function (exception, headers, body) { return { diff --git a/package.json b/package.json index 3e068e3..2ca4be7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.6.6", + "version": "2.6.7", "author": { "name": "XuChang" }, diff --git a/src/compiler/env.ts b/src/compiler/env.ts index f897866..f961ea4 100644 --- a/src/compiler/env.ts +++ b/src/compiler/env.ts @@ -29,6 +29,7 @@ export const ACTION_CONSTANT_IN_OAK_DOMAIN = (level = 2) => { export const RESERVED_ENTITIES = ['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; diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index 2105bf9..94db06f 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -14,6 +14,7 @@ import { NUMERICAL_LITERL_DEFAULT_SCALE, INT_LITERL_DEFAULT_WIDTH, LIB_OAK_DOMAIN, + ENTITY_NAME_MAX_LENGTH, } from './env'; import { firstLetterLowerCase, firstLetterUpperCase } from '../utils/string'; @@ -510,6 +511,19 @@ function getStringEnumValues(filename: string, program: ts.Program, obj: string, } } +function checkNameLegal(filename: string, attrName: string, upperCase?: boolean) { + assert(attrName.length <= ENTITY_NAME_MAX_LENGTH, `文件「${filename}」:「${attrName}」的名称定义过长,不能超过「${ENTITY_NAME_MAX_LENGTH}」长度`); + if (upperCase) { + assert(/[A-Z][a-z|A-Z|0-9]+/i.test(attrName), `文件「${filename}」:「${attrName}」的名称必须以大写字母开始,且只能包含字母和数字`); + } + else if (upperCase === false) { + assert(/[a-z][a-z|A-Z|0-9]+/i.test(attrName), `文件「${filename}」:「${attrName}」的名称必须以小写字母开始,且只能包含字母和数字`); + } + else { + assert(/[a-z|A-Z][a-z|A-Z|0-9]+/i.test(attrName), `文件「${filename}」:「${attrName}」的名称必须以字母开始,且只能包含字母和数字`); + } +} + function analyzeEntity(filename: string, path: string, program: ts.Program, relativePath?: string) { const fullPath = `${path}/${filename}`; const sourceFile = program.getSourceFile(fullPath); @@ -521,6 +535,8 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela // removeFromRelationShip(moduleName); console.warn(`出现了同名的Entity定义「${moduleName}」,将使用${fullPath}取代掉默认对象,请检查新的对象结构及相关常量定义与原有的兼容,否则原有对象的相关逻辑会出现不可知异常`); } + checkNameLegal(filename, moduleName, true); + const referencedSchemas: string[] = []; const schemaAttrs: ts.TypeElement[] = []; let hasFulltextIndex: boolean = false; @@ -587,6 +603,7 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela (attrNode) => { const { type, name, questionToken } = attrNode; const attrName = (name).text; + checkNameLegal(filename, attrName, false); if (ts.isTypeReferenceNode(type!) && ts.isIdentifier(type.typeName)) { if ((referencedSchemas.includes(type.typeName.text) || type.typeName.text === 'Schema')) { @@ -707,10 +724,22 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela [moduleName]: 1, }); } + else if (hasEntityAttr || hasEntityIdAttr) { + throw new Error(`文件「${filename}」:属性 定义中只包含${hasEntityAttr ? 'entity' : 'entityId'},不符合定义规范。entity/entityId必须联合出现,代表不定对象的反向指针`); + } beforeSchema = false; - // 对于不是Oper的对象,全部建立和OperEntity的反指关系 - if (!['Oper', 'OperEntity', 'ModiEntity'].includes(moduleName)) { + // 对于不是Modi和Oper的对象,全部建立和ModiEntity的反指关系 + if (!['Modi', 'Oper', 'OperEntity', 'ModiEntity'].includes(moduleName) && !toModi) { + if (ReversePointerRelations['ModiEntity'] && !ReversePointerRelations['ModiEntity'].includes(moduleName)) { + ReversePointerRelations['ModiEntity'].push(moduleName); + } + else { + assign(ReversePointerRelations, { + ['ModiEntity']: [moduleName], + }); + } + if (ReversePointerRelations['OperEntity'] && !ReversePointerRelations['OperEntity'].includes(moduleName)) { ReversePointerRelations['OperEntity'].push(moduleName); } @@ -719,19 +748,8 @@ function analyzeEntity(filename: string, path: string, program: ts.Program, rela ['OperEntity']: [moduleName], }); } - - // 对于不是Modi的对象,全部建立和ModiEntity的反指关系 - if (!['Modi'].includes(moduleName) && !toModi) { - if (ReversePointerRelations['ModiEntity'] && !ReversePointerRelations['ModiEntity'].includes(moduleName)) { - ReversePointerRelations['ModiEntity'].push(moduleName); - } - else { - assign(ReversePointerRelations, { - ['ModiEntity']: [moduleName], - }); - } - } } + } else if (beforeSchema) { // 本地规定的一些形状定义,直接使用 @@ -5453,7 +5471,7 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { factory.createArrayLiteralExpression( enumAttributes[(name).text].map( ele => factory.createStringLiteral(ele) - ) + ) ) ) ); @@ -5479,7 +5497,7 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { if (ts.isUnionTypeNode(type!)) { if (ts.isLiteralTypeNode(type.types[0])) { if (ts.isStringLiteral(type.types[0].literal)) { - assert (enumAttributes && enumAttributes[(name).text]); + assert(enumAttributes && enumAttributes[(name).text]); attrAssignments.push( factory.createPropertyAssignment( 'type', @@ -5490,7 +5508,7 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { factory.createArrayLiteralExpression( enumAttributes[(name).text].map( ele => factory.createStringLiteral(ele) - ) + ) ) ) ); diff --git a/src/entities/Oper.ts b/src/entities/Oper.ts index 40df381..1bf5364 100644 --- a/src/entities/Oper.ts +++ b/src/entities/Oper.ts @@ -9,6 +9,7 @@ export interface Schema extends EntityShape { filter?: Object; extra?: Object; operator?: User; + targetEntity: String<32>; }; const configuration: Configuration = { @@ -24,6 +25,7 @@ const locale: LocaleDef = { filter: '选择条件', extra: '其它', operator: '操作者', + targetEntity: '关联对象', }, }, }; diff --git a/src/store/AsyncRowStore.ts b/src/store/AsyncRowStore.ts index 254d389..a4939bd 100644 --- a/src/store/AsyncRowStore.ts +++ b/src/store/AsyncRowStore.ts @@ -4,7 +4,7 @@ import assert from "assert"; import { IncomingHttpHeaders } from "http"; export abstract class AsyncContext implements Context { - private rowStore: AsyncRowStore; + rowStore: AsyncRowStore; private uuid?: string; opRecords: OpRecord[]; private scene?: string; @@ -15,6 +15,11 @@ export abstract class AsyncContext implements Context { rollback: Array<() => Promise>; } + /** + * 在返回结果前调用,对数据行进行一些预处理,比如将一些敏感的列隐藏 + */ + abstract refineOpRecords(): Promise; + constructor(store: AsyncRowStore>, headers?: IncomingHttpHeaders) { this.rowStore = store; this.opRecords = []; diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index 9dbccfd..71f2519 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -4,19 +4,18 @@ import { OperateOption, SelectOption, OperationResult, CreateAtAttribute, UpdateAtAttribute, AggregationResult, DeleteAtAttribute } from "../types/Entity"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; -import { RowStore } from '../types/RowStore'; +import { OperationRewriter, RowStore, SelectionRewriter } from '../types/RowStore'; import { StorageSchema } from '../types/Storage'; import { addFilterSegment, combineFilters } from "./filter"; import { judgeRelation } from "./relation"; -import { OakRowUnexistedException } from "../types"; +import { EXPRESSION_PREFIX, getAttrRefInExpression, OakRowUnexistedException } from "../types"; import { unset, uniq, cloneDeep, pick } from '../utils/lodash'; import { SyncContext } from "./SyncRowStore"; import { AsyncContext } from "./AsyncRowStore"; import { getRelevantIds } from "./filter"; -import { CreateOperation as CreateOperOperation } from '../base-app-domain/Oper/Schema'; +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 { reinforceOperation, reinforceSelection } from "./selection"; /**这个用来处理级联的select和update,对不同能力的 */ export abstract class CascadeStore extends RowStore { @@ -26,6 +25,252 @@ export abstract class CascadeStore exten protected abstract supportManyToOneJoin(): boolean; protected abstract supportMultipleCreate(): boolean; + private selectionRewriters: SelectionRewriter[] = []; + private operationRewriters: OperationRewriter[] = []; + + private reinforceSelection(entity: keyof ED, selection: ED[keyof ED]['Selection']) { + const { filter, data, sorter } = selection; + + const checkNode = (projectionNode: ED[keyof ED]['Selection']['data'], attrs: string[]) => { + attrs.forEach( + (attr) => { + if (!projectionNode.hasOwnProperty(attr)) { + Object.assign(projectionNode, { + [attr]: 1, + }); + } + } + ); + }; + + let relevantIds: string[] = []; + if (filter) { + const toBeAssignNode: Record = {}; // 用来记录在表达式中涉及到的结点 + // filter当中所关联到的属性必须在projection中 + const filterNodeDict: Record = {}; + const checkFilterNode = (entity2: keyof ED, filterNode: ED[keyof ED]['Selection']['filter'], projectionNode: ED[keyof ED]['Selection']['data']) => { + const necessaryAttrs: string[] = ['id']; + for (const attr in filterNode) { + if (attr === '#id') { + assert(!filterNodeDict[filterNode[attr]!], `projection中结点的id有重复, ${filterNode[attr]}`); + Object.assign(filterNodeDict, { + [filterNode[attr]!]: projectionNode, + }); + if (toBeAssignNode[filterNode[attr]!]) { + checkNode(projectionNode, toBeAssignNode[filterNode[attr]!]); + } + } + else if (['$and', '$or'].includes(attr)) { + for (const node of filterNode[attr]!) { + checkFilterNode(entity2, node, projectionNode); + } + } + else if (attr === '$not') { + checkFilterNode(entity2, filterNode[attr]!, projectionNode); + } + else if (attr === '$text') { + // 全文检索首先要有fulltext索引,其次要把fulltext的相关属性加到projection里 + const { indexes } = this.getSchema()[entity2]; + + const fulltextIndex = indexes!.find( + ele => ele.config && ele.config.type === 'fulltext' + ); + + const { attributes } = fulltextIndex!; + necessaryAttrs.push(...(attributes.map(ele => ele.name as string))); + } + else { + if (attr.toLowerCase().startsWith(EXPRESSION_PREFIX)) { + const exprResult = getAttrRefInExpression(filterNode[attr]!); + for (const nodeName in exprResult) { + if (nodeName === '#current') { + checkNode(projectionNode, exprResult[nodeName]); + } + else if (filterNodeDict[nodeName]) { + checkNode(filterNodeDict[nodeName], exprResult[nodeName]); + } + else { + if (toBeAssignNode[nodeName]) { + toBeAssignNode[nodeName].push(...exprResult[nodeName]); + } + else { + Object.assign(toBeAssignNode, { + [nodeName]: exprResult[nodeName], + }); + } + } + } + } + else { + const rel = this.judgeRelation(entity2, attr); + if (rel === 1) { + necessaryAttrs.push(attr); + } + else if (rel === 2) { + // entity/entityId反指 + necessaryAttrs.push('entity', 'entityId'); + if (!projectionNode[attr]) { + Object.assign(projectionNode, { + [attr]: { + id: 1, + } + }); + } + checkFilterNode(attr, filterNode[attr]!, projectionNode[attr]); + } + else if (typeof rel === 'string') { + necessaryAttrs.push(`${attr}Id`); + if (!projectionNode[attr]) { + Object.assign(projectionNode, { + [attr]: { + id: 1, + } + }); + } + checkFilterNode(rel, filterNode[attr]!, projectionNode[attr]); + } + else if (rel instanceof Array) { + // 现在filter中还不支持一对多的语义,先放着吧 + } + } + } + checkNode(projectionNode, necessaryAttrs); + } + }; + + checkFilterNode(entity, filter, data); + relevantIds = getRelevantIds(filter); + } + + // sorter感觉现在取不取影响不大,前端的list直接获取返回的ids了,先不管之 + if (sorter) { + } + + const toBeAssignNode2: Record = {}; // 用来记录在表达式中涉及到的结点 + const projectionNodeDict: Record = {}; + const checkProjectionNode = (entity2: keyof ED, projectionNode: ED[keyof ED]['Selection']['data']) => { + const necessaryAttrs: string[] = ['id', '$$createAt$$']; // 有的页面依赖于其它页面取数据,有时两个页面的filter的差异会导致有一个加createAt,有一个不加,此时可能产生前台取数据不完整的异常。先统一加上 + for (const attr in projectionNode) { + if (attr === '#id') { + assert(!projectionNodeDict[projectionNode[attr]!], `projection中结点的id有重复, ${projectionNode[attr]}`); + Object.assign(projectionNodeDict, { + [projectionNode[attr]!]: projectionNode, + }); + if (toBeAssignNode2[projectionNode[attr]!]) { + checkNode(projectionNode, toBeAssignNode2[projectionNode[attr]!]); + } + } + else { + if (attr.toLowerCase().startsWith(EXPRESSION_PREFIX)) { + const exprResult = getAttrRefInExpression(projectionNode[attr]!); + for (const nodeName in exprResult) { + if (nodeName === '#current') { + checkNode(projectionNode, exprResult[nodeName]); + } + else if (projectionNodeDict[nodeName]) { + checkNode(projectionNodeDict[nodeName], exprResult[nodeName]); + } + else { + if (toBeAssignNode2[nodeName]) { + toBeAssignNode2[nodeName].push(...exprResult[nodeName]); + } + else { + Object.assign(toBeAssignNode2, { + [nodeName]: exprResult[nodeName], + }); + } + } + } + } + else { + const rel = 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(`${attr}Id`); + checkProjectionNode(rel, projectionNode[attr]); + } + else if (rel instanceof Array && !attr.endsWith('$$aggr')) { + const { data } = projectionNode[attr]; + if (rel[1]) { + checkNode(data, [rel[1]]); + } + else { + checkNode(data, ['entity', 'entityId']); + } + this.reinforceSelection(rel[0], projectionNode[attr]); + } + } + } + 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', + }, + } + }); + } + }; + checkProjectionNode(entity, data); + + if (!sorter && relevantIds.length === 0) { + // 如果没有sorter,就给予一个按createAt逆序的sorter + Object.assign(selection, { + sorter: [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'desc', + } + ] + }); + Object.assign(data, { + $$createAt$$: 1, + }); + } + + 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) + ); + } + + public registerOperationRewriter(rewriter: OperationRewriter) { + this.operationRewriters.push(rewriter); + } + + public registerSelectionRewriter(rewriter: SelectionRewriter) { + this.selectionRewriters.push(rewriter); + } + protected abstract selectAbjointRow>( entity: T, selection: ED[T]['Selection'], @@ -131,6 +376,15 @@ export abstract class CascadeStore exten else { cascadeSelectionFns.push( (result) => { + const entityIds = uniq(result.filter( + ele => ele.entity === attr + ).map( + ele => { + assert(ele.entityId !== null); + return ele.entityId; + } + ) as string[]); + const dealWithSubRows = (subRows: Partial[]) => { assert(subRows.length <= entityIds.length); if (subRows.length < entityIds.length && !toModi) { @@ -167,14 +421,6 @@ export abstract class CascadeStore exten } ); }; - const entityIds = uniq(result.filter( - ele => ele.entity === attr - ).map( - ele => { - assert(ele.entityId !== null); - return ele.entityId; - } - ) as string[]); if (entityIds.length > 0) { const subRows = cascadeSelectFn.call(this, attr as any, { @@ -194,9 +440,6 @@ export abstract class CascadeStore exten dealWithSubRows(subRows as any); } } - else { - - } } ); } @@ -248,6 +491,12 @@ export abstract class CascadeStore exten else { cascadeSelectionFns.push( (result) => { + const ids = uniq(result.filter( + ele => !!(ele[`${attr}Id`]) + ).map( + ele => ele[`${attr}Id`] + ) as string[]); + const dealWithSubRows = (subRows: Partial[]) => { assert(subRows.length <= ids.length); if (subRows.length < ids.length && !toModi) { @@ -289,11 +538,6 @@ export abstract class CascadeStore exten } ); }; - const ids = uniq(result.filter( - ele => !!(ele[`${attr}Id`]) - ).map( - ele => ele[`${attr}Id`] - ) as string[]); if (ids.length > 0) { const subRows = cascadeSelectFn.call(this, relation, { @@ -368,17 +612,26 @@ export abstract class CascadeStore exten ) as string[]; const dealWithSubRows = (subRows: Partial[]) => { - result.forEach( - (ele) => { - const subRowss = subRows.filter( - ele2 => ele2[foreignKey] === ele.id - ); - assert(subRowss); - Object.assign(ele, { - [attr]: subRowss, - }); - } - ); + // 这里如果result只有一行,则把返回结果直接置上,不对比外键值 + // 这样做的原因是有的对象的filter会被改写掉(userId),只能临时这样处理 + if (result.length == 1) { + Object.assign(result[0], { + [attr]: subRows, + }); + } + else { + result.forEach( + (ele) => { + const subRowss = subRows.filter( + ele2 => ele2[foreignKey] === ele.id + ); + assert(subRowss); + Object.assign(ele, { + [attr]: subRowss, + }); + } + ); + } }; if (ids.length > 0) { @@ -452,17 +705,26 @@ export abstract class CascadeStore exten ele => ele.id ) as string[]; const dealWithSubRows = (subRows: Partial[]) => { - result.forEach( - (ele) => { - const subRowss = subRows.filter( - ele2 => ele2.entityId === ele.id - ); - assert(subRowss); - Object.assign(ele, { - [attr]: subRowss, - }); - } - ); + // 这里如果result只有一行,则把返回结果直接置上,不对比外键值 + // 这样做的原因是有的对象的filter会被改写掉(userId),只能临时这样处理 + if (result.length === 1) { + Object.assign(result[0], { + [attr]: subRows, + }); + } + else { + result.forEach( + (ele) => { + const subRowss = subRows.filter( + ele2 => ele2.entityId === ele.id + ); + assert(subRowss); + Object.assign(ele, { + [attr]: subRowss, + }); + } + ); + } }; if (ids.length > 0) { @@ -1038,9 +1300,9 @@ export abstract class CascadeStore exten if (!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string)) { // 按照框架要求生成Oper和OperEntity这两个内置的对象 assert(operId); - const operatorId = await context.getCurrentUserId(true); + const operatorId = context.getCurrentUserId(true); if (operatorId) { - const createOper: CreateOperOperation = { + const createOper: CreateSingleOperOperation = { id: 'dummy', action: 'create', data: { @@ -1048,6 +1310,7 @@ export abstract class CascadeStore exten action, data, operatorId, + targetEntity: entity as string, operEntity$oper: data instanceof Array ? { id: 'dummy', action: 'create', @@ -1055,8 +1318,8 @@ export abstract class CascadeStore exten data.map( async (ele) => ({ id: await generateNewIdAsync(), - entity: entity as string, entityId: ele.id, + entity: entity as string, }) ) ), @@ -1065,8 +1328,8 @@ export abstract class CascadeStore exten action: 'create', data: { id: await generateNewIdAsync(), - entity: entity as string, entityId: (data as ED[T]['CreateSingle']['data']).id, + entity: entity as string, }, }] }, @@ -1190,13 +1453,14 @@ export abstract class CascadeStore exten if (!option?.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string) && ids.length > 0) { // 按照框架要求生成Oper和OperEntity这两个内置的对象 assert(operId); - const createOper: CreateOperOperation = { + const createOper: CreateSingleOperOperation = { id: 'dummy', action: 'create', data: { id: operId, action, data, + targetEntity: entity as string, operEntity$oper: { id: 'dummy', action: 'create', @@ -1204,8 +1468,8 @@ export abstract class CascadeStore exten ids.map( async (ele) => ({ id: await generateNewIdAsync(), - entity: entity as string, entityId: ele, + entity: entity as string, }) ) ) @@ -1344,7 +1608,7 @@ export abstract class CascadeStore exten operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult { - reinforceOperation(this.getSchema(), entity, operation); + this.reinforceOperation(entity, operation); const { action, data, filter, id } = operation; let opData: any; const wholeBeforeFns: Array<() => any> = []; @@ -1409,7 +1673,7 @@ export abstract class CascadeStore exten operation: ED[T]['Operation'], context: Cxt, option: OP): Promise> { - reinforceOperation(this.getSchema(), entity, operation); + this.reinforceOperation(entity, operation); const { action, data, filter, id } = operation; let opData: any; const wholeBeforeFns: Array<() => Promise> = []; @@ -1472,7 +1736,7 @@ export abstract class CascadeStore exten selection: ED[T]['Selection'], context: Cxt, option: OP): Partial[] { - reinforceSelection(this.getSchema(), entity, selection); + this.reinforceSelection(entity, selection); const { data, filter, indexFrom, count, sorter } = selection; const { projection, cascadeSelectionFns } = this.destructCascadeSelect( entity, @@ -1571,11 +1835,11 @@ export abstract class CascadeStore exten const { id } = row; if (!entityBranch![id!]) { Object.assign(entityBranch!, { - [id!]: row, + [id!]: cloneDeep(row), }); } else { - Object.assign(entityBranch[id], row); + Object.assign(entityBranch[id], cloneDeep(row)); } } } @@ -1597,7 +1861,7 @@ export abstract class CascadeStore exten if (row) { const { id } = row as { id: string }; Object.assign(entityBranch!, { - [id!]: row, + [id!]: cloneDeep(row), }); } } @@ -1612,7 +1876,7 @@ export abstract class CascadeStore exten selection: ED[T]['Selection'], context: Cxt, option: OP): Promise[]> { - reinforceSelection(this.getSchema(), entity, selection); + this.reinforceSelection(entity, selection); const { data, filter, indexFrom, count, sorter } = selection; const { projection, cascadeSelectionFns } = this.destructCascadeSelect( entity, diff --git a/src/store/SyncRowStore.ts b/src/store/SyncRowStore.ts index 45fe0bb..95f692f 100644 --- a/src/store/SyncRowStore.ts +++ b/src/store/SyncRowStore.ts @@ -2,7 +2,7 @@ import assert from 'assert'; import { EntityDict, RowStore, OperateOption, OperationResult, SelectOption, TxnOption, Context, AggregationResult } from "../types"; export abstract class SyncContext implements Context { - private rowStore: SyncRowStore; + rowStore: SyncRowStore; private uuid?: string; constructor(rowStore: SyncRowStore>) { this.rowStore = rowStore; diff --git a/src/store/TriggerExecutor.ts b/src/store/TriggerExecutor.ts index c7888c2..3b7c726 100644 --- a/src/store/TriggerExecutor.ts +++ b/src/store/TriggerExecutor.ts @@ -49,7 +49,7 @@ export class TriggerExecutor { registerChecker>(checker: Checker): void { const { entity, action, type, conditionalFilter } = checker; const triggerName = `${String(entity)}${action}权限检查-${this.counter++}`; - const { fn, when } = translateCheckerInAsyncContext(checker, true); + const { fn, when } = translateCheckerInAsyncContext(checker); const priority = type === 'data' ? DATA_CHECKER_DEFAULT_PRIORITY : CHECKER_DEFAULT_PRIORITY; // checker的默认优先级最低(前面的trigger可能会赋上一些相应的值) const trigger = { checkerType: type, diff --git a/src/store/checker.ts b/src/store/checker.ts index 2b30d91..2efede7 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -24,7 +24,7 @@ export function translateCheckerInAsyncContext< ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext ->(checker: Checker, silent?: boolean): { +>(checker: Checker): { fn: Trigger['fn']; when: 'before' | 'after'; } { @@ -48,7 +48,7 @@ export function translateCheckerInAsyncContext< const fn = (async ({ operation }, context, option) => { const { filter: operationFilter, action } = operation; const filter2 = typeof filter === 'function' ? await (filter as Function)(operation, context, option) : filter; - if (silent) { + if (['select', 'count', 'stat'].includes(action)) { operation.filter = addFilterSegment(operationFilter || {}, filter2); return 0; } @@ -106,7 +106,7 @@ export function translateCheckerInAsyncContext< console.warn(`${entity as string}对象的create类型的checker中,存在无法转换为表达式形式的情况,请尽量使用authDef格式定义这类checker`); return 0; } - if (silent) { + if (['select', 'count', 'stat'].includes(action)) { operation.filter = addFilterSegment(filter || {}, result); return 0; } @@ -405,7 +405,7 @@ function translateCascadeRelationFilterMaker translateCreateFilterMaker(entity, ele, userId) ) as ReturnType[]; // or也只要有一个满足就行(不能否定) diff --git a/src/store/selection.ts b/src/store/selection.ts deleted file mode 100644 index 0c4842f..0000000 --- a/src/store/selection.ts +++ /dev/null @@ -1,275 +0,0 @@ -import assert from 'assert'; -import { getAttrRefInExpression, StorageSchema } from '../types'; -import { EXPRESSION_PREFIX } from '../types/Demand'; -import { EntityDict } from '../types/Entity'; -import { getRelevantIds } from './filter'; -import { judgeRelation } from './relation'; - -type SelectionRewriter = (schema: StorageSchema, entity: keyof ED, selection: ED[keyof ED]['Selection']) => void; - -const SelectionRewriters: SelectionRewriter[] = []; - -export function registerSelectionRewriter(rewriter: SelectionRewriter) { - SelectionRewriters.push(rewriter); -} - -function getSelectionRewriters() { - return SelectionRewriters as SelectionRewriter[]; -} - -type OperationRewriter = (schema: StorageSchema, entity: keyof ED, operate: ED[keyof ED]['Operation']) => void; - -const OperationRewriters: OperationRewriter[] = []; - -export function registerOperationRewriter(rewriter: OperationRewriter) { - OperationRewriters.push(rewriter); -} - -function getOperationRewriters() { - return OperationRewriters as OperationRewriter[]; -} - -/** - * 对selection进行一些完善,避免编程人员的疏漏 - * @param selection - */ -export function reinforceSelection(schema: StorageSchema, entity: keyof ED, selection: ED[keyof ED]['Selection']) { - const { filter, data, sorter } = selection; - - const checkNode = (projectionNode: ED[keyof ED]['Selection']['data'], attrs: string[]) => { - attrs.forEach( - (attr) => { - if (!projectionNode.hasOwnProperty(attr)) { - Object.assign(projectionNode, { - [attr]: 1, - }); - } - } - ); - }; - - let relevantIds: string[] = []; - if (filter) { - const toBeAssignNode: Record = {}; // 用来记录在表达式中涉及到的结点 - // filter当中所关联到的属性必须在projection中 - const filterNodeDict: Record = {}; - const checkFilterNode = (entity2: keyof ED, filterNode: ED[keyof ED]['Selection']['filter'], projectionNode: ED[keyof ED]['Selection']['data']) => { - const necessaryAttrs: string[] = ['id']; - for (const attr in filterNode) { - if (attr === '#id') { - assert(!filterNodeDict[filterNode[attr]!], `projection中结点的id有重复, ${filterNode[attr]}`); - Object.assign(filterNodeDict, { - [filterNode[attr]!]: projectionNode, - }); - if (toBeAssignNode[filterNode[attr]!]) { - checkNode(projectionNode, toBeAssignNode[filterNode[attr]!]); - } - } - else if (['$and', '$or'].includes(attr)) { - for (const node of filterNode[attr]!) { - checkFilterNode(entity2, node, projectionNode); - } - } - else if (attr === '$not') { - checkFilterNode(entity2, filterNode[attr]!, projectionNode); - } - else if (attr === '$text') { - // 全文检索首先要有fulltext索引,其次要把fulltext的相关属性加到projection里 - const { indexes } = schema[entity2]; - - const fulltextIndex = indexes!.find( - ele => ele.config && ele.config.type === 'fulltext' - ); - - const { attributes } = fulltextIndex!; - necessaryAttrs.push(...(attributes.map(ele => ele.name as string))); - } - else { - if (attr.toLowerCase().startsWith(EXPRESSION_PREFIX)) { - const exprResult = getAttrRefInExpression(filterNode[attr]!); - for (const nodeName in exprResult) { - if (nodeName === '#current') { - checkNode(projectionNode, exprResult[nodeName]); - } - else if (filterNodeDict[nodeName]) { - checkNode(filterNodeDict[nodeName], exprResult[nodeName]); - } - else { - if (toBeAssignNode[nodeName]) { - toBeAssignNode[nodeName].push(...exprResult[nodeName]); - } - else { - Object.assign(toBeAssignNode, { - [nodeName]: exprResult[nodeName], - }); - } - } - } - } - else { - const rel = judgeRelation(schema, entity2, attr); - if (rel === 1) { - necessaryAttrs.push(attr); - } - else if (rel === 2) { - // entity/entityId反指 - necessaryAttrs.push('entity', 'entityId'); - if (!projectionNode[attr]) { - Object.assign(projectionNode, { - [attr]: { - id: 1, - } - }); - } - checkFilterNode(attr, filterNode[attr]!, projectionNode[attr]); - } - else if (typeof rel === 'string') { - necessaryAttrs.push(`${attr}Id`); - if (!projectionNode[attr]) { - Object.assign(projectionNode, { - [attr]: { - id: 1, - } - }); - } - checkFilterNode(rel, filterNode[attr]!, projectionNode[attr]); - } - else if (rel instanceof Array) { - // 现在filter中还不支持一对多的语义,先放着吧 - } - } - } - checkNode(projectionNode, necessaryAttrs); - } - }; - - checkFilterNode(entity, filter, data); - relevantIds = getRelevantIds(filter); - } - - // sorter感觉现在取不取影响不大,前端的list直接获取返回的ids了,先不管之 - if (sorter) { - } - - const toBeAssignNode2: Record = {}; // 用来记录在表达式中涉及到的结点 - const projectionNodeDict: Record = {}; - const checkProjectionNode = (entity2: keyof ED, projectionNode: ED[keyof ED]['Selection']['data']) => { - const necessaryAttrs: string[] = ['id', '$$createAt$$']; // 有的页面依赖于其它页面取数据,有时两个页面的filter的差异会导致有一个加createAt,有一个不加,此时可能产生前台取数据不完整的异常。先统一加上 - for (const attr in projectionNode) { - if (attr === '#id') { - assert(!projectionNodeDict[projectionNode[attr]!], `projection中结点的id有重复, ${projectionNode[attr]}`); - Object.assign(projectionNodeDict, { - [projectionNode[attr]!]: projectionNode, - }); - if (toBeAssignNode2[projectionNode[attr]!]) { - checkNode(projectionNode, toBeAssignNode2[projectionNode[attr]!]); - } - } - else { - if (attr.toLowerCase().startsWith(EXPRESSION_PREFIX)) { - const exprResult = getAttrRefInExpression(projectionNode[attr]!); - for (const nodeName in exprResult) { - if (nodeName === '#current') { - checkNode(projectionNode, exprResult[nodeName]); - } - else if (projectionNodeDict[nodeName]) { - checkNode(projectionNodeDict[nodeName], exprResult[nodeName]); - } - else { - if (toBeAssignNode2[nodeName]) { - toBeAssignNode2[nodeName].push(...exprResult[nodeName]); - } - else { - Object.assign(toBeAssignNode2, { - [nodeName]: exprResult[nodeName], - }); - } - } - } - } - else { - const rel = judgeRelation(schema, 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(`${attr}Id`); - checkProjectionNode(rel, projectionNode[attr]); - } - else if (rel instanceof Array && !attr.endsWith('$$aggr')) { - const { data } = projectionNode[attr]; - if (rel[1]) { - checkNode(data, [rel[1]]); - } - else { - checkNode(data, ['entity', 'entityId']); - } - reinforceSelection(schema, rel[0], projectionNode[attr]); - } - } - } - checkNode(projectionNode, necessaryAttrs); - } - - // 如果对象中指向一对多的Modi,此时加上指向Modi的projection - if (schema[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', - }, - } - }); - } - }; - checkProjectionNode(entity, data); - - if (!sorter && relevantIds.length === 0) { - // 如果没有sorter,就给予一个按createAt逆序的sorter - Object.assign(selection, { - sorter: [ - { - $attr: { - $$createAt$$: 1, - }, - $direction: 'desc', - } - ] - }); - Object.assign(data, { - $$createAt$$: 1, - }); - } - - SelectionRewriters.forEach( - ele => ele(schema, entity, selection) - ); -} - -/** - * 对operation进行一些完善,作为operation算子的注入点 - * @param schema - * @param entity - * @param selection - */ -export function reinforceOperation(schema: StorageSchema, entity: keyof ED, operation: ED[keyof ED]['Operation']) { - OperationRewriters.forEach( - ele => ele(schema, entity, operation) - ); -} \ No newline at end of file diff --git a/src/timers/oper.ts b/src/timers/oper.ts new file mode 100644 index 0000000..f930642 --- /dev/null +++ b/src/timers/oper.ts @@ -0,0 +1,67 @@ +import { EntityDict } from '../types/Entity'; +import { EntityDict as BaseEntityDict } from '../base-app-domain'; +import { AsyncContext } from '../store/AsyncRowStore'; +import { vaccumEntities } from './vaccum'; +import { combineFilters } from '../store/filter'; + +export type VaccumOperOption = { + aliveLine: number; + excludeOpers?: { + [T in keyof ED]?: ED[T]['Action'][]; + }; + backupDir?: string; + zip?: boolean; +}; + +/** + * 将一定日期之前的oper对象清空 + * @param option + * @param context + * @returns + */ +export async function vaccumOper>(option: VaccumOperOption, context: Cxt) { + const { aliveLine, excludeOpers, ...rest } = option; + + const operFilter: ED['oper']['Selection']['filter'] = {}; + if (excludeOpers) { + const notFilters: ED['oper']['Selection']['filter'][] = []; + for (const key in excludeOpers) { + if (excludeOpers[key]!.length > 0) { + notFilters.push({ + targetEntity: key, + action: { + $in: excludeOpers[key], + } + } as ED['oper']['Selection']['filter']); + } + else { + notFilters.push({ + targetEntity: key, + }); + } + } + if (notFilters.length > 0) { + operFilter.$not = { + $or: notFilters as NonNullable[], + }; + } + } + return vaccumEntities({ + entities: [{ + entity: 'operEntity', + aliveLine: aliveLine + 10000, + filter: { + oper: combineFilters([operFilter, { + $$createAt$$: { + $lt: aliveLine, + } + }]), + }, + }, { + entity: 'oper', + aliveLine, + filter: operFilter, + }], + ...rest, + }, context); +} \ No newline at end of file diff --git a/src/timers/vaccum.ts b/src/timers/vaccum.ts new file mode 100644 index 0000000..cac0f6e --- /dev/null +++ b/src/timers/vaccum.ts @@ -0,0 +1,134 @@ +import dayJs from 'dayjs'; +import { appendFileSync, existsSync, openSync, rmSync, closeSync, createReadStream, createWriteStream } from 'fs'; +import { EntityDict } from '../types/Entity'; +import { EntityDict as BaseEntityDict } from '../base-app-domain'; +import { AsyncContext } from '../store/AsyncRowStore'; +import { combineFilters } from '../store/filter'; +import { createGzip } from 'node:zlib'; +import { pipeline } from 'stream'; +import { generateNewIdAsync } from '../utils/uuid'; + +type VaccumOptionEntity = { + entity: T; + filter?: ED[T]['Selection']['filter']; // 如果有额外的条件,放在filter中(满足条件的才会被清空) + aliveLine: number; // vaccum一定是按createAt清空数据,在aliveLine之后的数据不会被清空 +}; + +type VaccumOption = { + entities: Array>; + backupDir?: string; + zip?: boolean; +}; + +/** + * 删除数据库中的部分数据,减少体积 + * 一般只删除日志类数据 + * @param option + */ +export async function vaccumEntities>(option: VaccumOption, context: Cxt) { + const { entities, backupDir } = option; + for (const ele of entities) { + const { entity, filter, aliveLine } = ele; + + let filter2: ED[keyof ED]['Selection']['filter'] = { + $$createAt$$: { + $lt: aliveLine, + }, + }; + if (filter) { + filter2 = combineFilters([filter2, filter]); + } + if (backupDir) { + // 使用mysqldump将待删除的数据备份出来 + const { zip: zip } = option; + const now = dayJs(); + const backFile = `${backupDir}/${entity as string}-${now.format('YYYY-MM-DD HH:mm:ss')}.csv`; + if (existsSync(backFile)) { + rmSync(backFile); + } + const fd = openSync(backFile, 'a'); + const attributes = ['id', '$$createAt$$', '$$updateAt$$', '$$deleteAt$$']; + const projection: ED[keyof ED]['Selection']['data']= { + id: 1, + $$createAt$$: 1, + $$updateAt$$: 1, + $$deleteAt$$: 1, + }; + for (const attr in context.getSchema()[entity]!.attributes) { + Object.assign(projection, { + [attr]: 1, + }); + attributes.push(attr); + } + appendFileSync(fd, attributes.join(',')); + appendFileSync(fd, '\n'); + + + let count = 0; + const appendData = async (minCreateAt: number): Promise => { + const filter3 = combineFilters([filter2, { + $$createAt$$: { + $gt: minCreateAt, + }, + }]); + const rows = await context.select(entity, { + data: projection, + filter: filter3, + sorter: [{ + $attr: { + $$createAt$$: 1, + }, + $direction: 'asc' + }], + indexFrom: 0, + count: 1000, + }, { includedDeleted: true }); + const csvTxt = rows.map( + (row) => attributes.map( + (attr) => JSON.stringify(row[attr]) + ).join(',') + ).join('\n'); + appendFileSync(fd, csvTxt); + appendFileSync(fd, '\n'); + count += rows.length; + if (rows.length === 1000) { + const maxCreateAt = rows[999].$$createAt$$; + return appendData(maxCreateAt as number); + } + }; + + await appendData(0); + closeSync(fd); + console.log(`备份${entity as string}对象完毕,共备份了${count}行数据`); + + if (count === 0) { + rmSync(backFile); + } + else if (zip) { + const gzip = createGzip(); + const source = createReadStream(backFile); + const destination = createWriteStream(`${backFile}.zip`); + await new Promise( + (resolve, reject) => { + pipeline(source, gzip, destination, (err) => { + if (err) { + reject(err); + } + else { + resolve(undefined); + } + }) + } + ); + } + } + + // 将对应的数据删除 + await context.operate(entity, { + id: await generateNewIdAsync(), + action: 'remove', + data: {}, + filter: filter2, + }, { deletePhysically: true }); + } +} \ No newline at end of file diff --git a/src/types/Connector.ts b/src/types/Connector.ts index 737f57e..eb3e15b 100644 --- a/src/types/Connector.ts +++ b/src/types/Connector.ts @@ -16,10 +16,10 @@ export abstract class Connector): Promise<{ name: string; params: any; context: BackCxt; }>; - abstract serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): { + abstract serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): Promise<{ body: any; headers?: Record; - }; + }>; abstract serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; diff --git a/src/types/Entity.ts b/src/types/Entity.ts index a1fb534..35f1ff3 100644 --- a/src/types/Entity.ts +++ b/src/types/Entity.ts @@ -43,6 +43,7 @@ export type OperateOption = { allowExists?: boolean; // 插入时允许已经存在唯一键值的行了,即insert / update逻辑 modiParentId?: string; // 如果是延时更新,相关modi要关联到一个父亲上统一应用 modiParentEntity?: string; // 如果是延时更新,相关modi要关联到一个父亲上统一应用 + deletePhysically?: boolean; dummy?: 1; // 无用,为了继承Option通过编译 }; diff --git a/src/types/RowStore.ts b/src/types/RowStore.ts index 4b2ba94..a0a150b 100644 --- a/src/types/RowStore.ts +++ b/src/types/RowStore.ts @@ -6,6 +6,10 @@ export type TxnOption = { isolationLevel: 'repeatable read' | 'serializable'; }; +export type SelectionRewriter = (schema: StorageSchema, entity: keyof ED, selection: ED[keyof ED]['Selection']) => void; +export type OperationRewriter = (schema: StorageSchema, entity: keyof ED, operate: ED[keyof ED]['Operation']) => void; + + export abstract class RowStore { protected storageSchema: StorageSchema; @@ -13,6 +17,10 @@ export abstract class RowStore { this.storageSchema = storageSchema; } + abstract registerOperationRewriter(rewriter: OperationRewriter): void; + + abstract registerSelectionRewriter(rewriter: SelectionRewriter): void; + getSchema () { return this.storageSchema; } diff --git a/src/utils/SimpleConnector.ts b/src/utils/SimpleConnector.ts index cdcc458..d73e99b 100644 --- a/src/utils/SimpleConnector.ts +++ b/src/utils/SimpleConnector.ts @@ -104,12 +104,14 @@ export class SimpleConnector | undefined; } { + async serializeResult(result: any, context: BackCxt, headers: IncomingHttpHeaders, body: any): Promise<{ body: any; headers?: Record | undefined; }> { if (result instanceof Stream || result instanceof Buffer) { return { body: result, }; } + + await context.refineOpRecords(); return { body: { result,