diff --git a/lib/store/CascadeStore.js b/lib/store/CascadeStore.js index dac2f0f..d886ec5 100644 --- a/lib/store/CascadeStore.js +++ b/lib/store/CascadeStore.js @@ -11,6 +11,7 @@ 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); @@ -1252,6 +1253,7 @@ 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 action = operation.action, data = operation.data, filter = operation.filter, id = operation.id; var opData; var wholeBeforeFns = []; @@ -1328,6 +1330,7 @@ var CascadeStore = /** @class */ (function (_super) { return tslib_1.__generator(this, function (_h) { switch (_h.label) { case 0: + (0, selection_1.reinforceOperation)(this.getSchema(), entity, operation); action = operation.action, data = operation.data, filter = operation.filter, id = operation.id; wholeBeforeFns = []; wholeAfterFns = []; @@ -1426,6 +1429,7 @@ var CascadeStore = /** @class */ (function (_super) { }); }; CascadeStore.prototype.cascadeSelect = function (entity, selection, context, option) { + (0, selection_1.reinforceSelection)(this.getSchema(), 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, { @@ -1547,6 +1551,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); 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, { diff --git a/lib/store/checker.js b/lib/store/checker.js index 23916de..38523ef 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -44,20 +44,19 @@ function translateCheckerInAsyncContext(checker) { var fn = (function (_a, context, option) { var operation = _a.operation; return tslib_1.__awaiter(_this, void 0, void 0, function () { - var operationFilter, action, filter2, _b, entity2, selection2, rows2, data_1, rows2, data_2; - var _c, _d; - return tslib_1.__generator(this, function (_e) { - switch (_e.label) { + var operationFilter, action, filter2, _b, entity2, selection2, rows2, e, rows2, e; + return tslib_1.__generator(this, function (_c) { + switch (_c.label) { case 0: operationFilter = operation.filter, action = operation.action; if (!(typeof filter_2 === 'function')) return [3 /*break*/, 2]; return [4 /*yield*/, filter_2(operation, context, option)]; case 1: - _b = _e.sent(); + _b = _c.sent(); return [3 /*break*/, 3]; case 2: _b = filter_2; - _e.label = 3; + _c.label = 3; case 3: filter2 = _b; if (!['select', 'count', 'stat'].includes(action)) return [3 /*break*/, 4]; @@ -65,7 +64,7 @@ function translateCheckerInAsyncContext(checker) { return [2 /*return*/, 0]; case 4: return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter || {}, true)]; case 5: - if (_e.sent()) { + if (_c.sent()) { return [2 /*return*/, 0]; } if (!inconsistentRows_1) return [3 /*break*/, 7]; @@ -75,20 +74,10 @@ function translateCheckerInAsyncContext(checker) { blockTrigger: true, })]; case 6: - rows2 = _e.sent(); - data_1 = {}; - rows2.forEach(function (ele) { - var _a; - return Object.assign(data_1, (_a = {}, - _a[ele.id] = ele, - _a)); - }); - throw new Exception_1.OakRowInconsistencyException({ - a: 's', - d: (_c = {}, - _c[entity2] = data_1, - _c) - }, errMsg_1); + rows2 = _c.sent(); + e = new Exception_1.OakRowInconsistencyException(undefined, errMsg_1); + e.addData(entity2, rows2); + throw e; case 7: return [4 /*yield*/, context.select(entity, { data: (0, actionDef_1.getFullProjection)(entity, context.getSchema()), filter: Object.assign({}, operationFilter, { @@ -99,20 +88,10 @@ function translateCheckerInAsyncContext(checker) { blockTrigger: true, })]; case 8: - rows2 = _e.sent(); - data_2 = {}; - rows2.forEach(function (ele) { - var _a; - return Object.assign(data_2, (_a = {}, - _a[ele.id] = ele, - _a)); - }); - throw new Exception_1.OakRowInconsistencyException({ - a: 's', - d: (_d = {}, - _d[entity] = data_2, - _d) - }, errMsg_1); + rows2 = _c.sent(); + e = new Exception_1.OakRowInconsistencyException(undefined, errMsg_1); + e.addData(entity, rows2); + throw e; } }); }); @@ -228,7 +207,8 @@ function translateCheckerInSyncContext(checker) { if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter, true)) { return; } - throw new Exception_1.OakRowInconsistencyException(undefined, errMsg_3); + var e = new Exception_1.OakRowInconsistencyException(undefined, errMsg_3); + throw e; } }; return { @@ -578,8 +558,8 @@ function createRemoveCheckers(schema, authDict) { var promises = []; if (OneToManyMatrix[entity]) { var _loop_5 = function (otm) { - var _g, _h, _j, _k; - var _l = tslib_1.__read(otm, 2), e = _l[0], attr = _l[1]; + var _g, _h; + var _j = tslib_1.__read(otm, 2), e = _j[0], attr = _j[1]; var proj = (_g = { id: 1 }, @@ -596,33 +576,20 @@ function createRemoveCheckers(schema, authDict) { }, { dontCollect: true }); if (result instanceof Promise) { promises.push(result.then(function (_a) { - var _b, _c; - var _d = tslib_1.__read(_a, 1), row = _d[0]; + var _b = tslib_1.__read(_a, 1), row = _b[0]; if (row) { - var record = { - a: 's', - d: (_b = {}, - _b[e] = (_c = {}, - _c[row.id] = row, - _c), - _b) - }; - throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(e, "\u300D\u5173\u8054\u7684\u884C")); + var err = new Exception_1.OakRowInconsistencyException(undefined, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(e, "\u300D\u5173\u8054\u7684\u884C")); + err.addData(e, [row]); + throw err; } })); } else { - var _m = tslib_1.__read(result, 1), row = _m[0]; + var _k = tslib_1.__read(result, 1), row = _k[0]; if (row) { - var record = { - a: 's', - d: (_j = {}, - _j[e] = (_k = {}, - _k[row.id] = row, - _k), - _j) - }; - throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(e, "\u300D\u5173\u8054\u7684\u884C")); + var err = new Exception_1.OakRowInconsistencyException(undefined, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(e, "\u300D\u5173\u8054\u7684\u884C")); + err.addData(e, [row]); + throw err; } } }; @@ -642,15 +609,15 @@ function createRemoveCheckers(schema, authDict) { } if (OneToManyOnEntityMatrix[entity]) { var _loop_6 = function (otm) { - var _o, _p, _q; + var _l, _m, _o; var proj = { id: 1, entity: 1, entityId: 1, }; - var filter = operation.filter && (_o = {}, - _o[entity] = operation.filter, - _o); + var filter = operation.filter && (_l = {}, + _l[entity] = operation.filter, + _l); var result = context.select(otm, { data: proj, filter: filter, @@ -659,33 +626,28 @@ function createRemoveCheckers(schema, authDict) { }, { dontCollect: true }); if (result instanceof Promise) { promises.push(result.then(function (_a) { - var _b, _c; - var _d = tslib_1.__read(_a, 1), row = _d[0]; + var _b = tslib_1.__read(_a, 1), row = _b[0]; if (row) { - var record = { - a: 's', - d: (_b = {}, - _b[otm] = (_c = {}, - _c[row.id] = row, - _c), - _b) - }; - throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(otm, "\u300D\u5173\u8054\u7684\u884C")); + var e = new Exception_1.OakRowInconsistencyException(undefined, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(otm, "\u300D\u5173\u8054\u7684\u884C")); + e.addData(otm, [row]); + throw e; } })); } else { - var _r = tslib_1.__read(result, 1), row = _r[0]; + var _p = tslib_1.__read(result, 1), row = _p[0]; if (row) { var record = { a: 's', - d: (_p = {}, - _p[otm] = (_q = {}, - _q[row.id] = row, - _q), - _p) + d: (_m = {}, + _m[otm] = (_o = {}, + _o[row.id] = row, + _o), + _m) }; - throw new Exception_1.OakRowInconsistencyException(record, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(otm, "\u300D\u5173\u8054\u7684\u884C")); + var e = new Exception_1.OakRowInconsistencyException(undefined, "\u60A8\u65E0\u6CD5\u5220\u9664\u5B58\u5728\u6709\u6548\u6570\u636E\u300C".concat(otm, "\u300D\u5173\u8054\u7684\u884C")); + e.addData(otm, [row]); + throw e; } } }; diff --git a/lib/store/selection.d.ts b/lib/store/selection.d.ts index 9986889..7999cb4 100644 --- a/lib/store/selection.d.ts +++ b/lib/store/selection.d.ts @@ -1,7 +1,19 @@ 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 index 11faa26..c58d32f 100644 --- a/lib/store/selection.js +++ b/lib/store/selection.js @@ -1,18 +1,37 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.reinforceSelection = void 0; +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; + Object.assign(data, { + '$$createAt$$': 1, + }); // 有的页面依赖于其它页面取数据,有时两个页面的filter的差异会导致有一个加createAt,有一个不加,此时可能产生前台取数据不完整的异常。先统一加上 var checkNode = function (projectionNode, attrs) { attrs.forEach(function (attr) { var _a; @@ -234,5 +253,16 @@ function reinforceSelection(schema, entity, selection) { $$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/types/Auth.d.ts b/lib/types/Auth.d.ts index 3b62fd3..01ddf18 100644 --- a/lib/types/Auth.d.ts +++ b/lib/types/Auth.d.ts @@ -61,7 +61,7 @@ export declare type AuthDef = { relationAuth?: CascadeRelationAuth>; actionAuth?: CascadeActionAuth; cascadeRemove?: { - [E in keyof ED[T]['OpSchema'] | '@entity']?: ActionOnRemove; + [E in (keyof ED | '@entity')]?: ActionOnRemove; }; }; export declare type AuthDefDict = { diff --git a/lib/types/Connector.d.ts b/lib/types/Connector.d.ts index bfef02a..f70c2e3 100644 --- a/lib/types/Connector.d.ts +++ b/lib/types/Connector.d.ts @@ -19,7 +19,7 @@ export declare abstract class Connector; }; - abstract serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { + 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 9e71be5..2baa18a 100644 --- a/lib/types/Entity.d.ts +++ b/lib/types/Entity.d.ts @@ -165,7 +165,7 @@ export declare type SelectOpResult = { a: 's'; d: { [T in keyof ED]?: { - [ID: string]: ED[T]['OpSchema']; + [ID: string]: Partial; }; }; }; diff --git a/lib/types/Exception.d.ts b/lib/types/Exception.d.ts index 80948c5..56e3596 100644 --- a/lib/types/Exception.d.ts +++ b/lib/types/Exception.d.ts @@ -1,11 +1,14 @@ -import { EntityDict, OpRecord } from "./Entity"; -export declare class OakException extends Error { +import { EntityDict, OpRecord, SelectOpResult } from "./Entity"; +export declare class OakException extends Error { + opRecord: SelectOpResult; constructor(message?: string); + addData(entity: T, rows: Partial[]): void; + setOpRecords(opRecord: SelectOpResult): void; toString(): string; } -export declare class OakDataException extends OakException { +export declare class OakDataException extends OakException { } -export declare class OakUniqueViolationException extends OakException { +export declare class OakUniqueViolationException extends OakException { rows: Array<{ id?: string; attrs: string[]; @@ -15,14 +18,14 @@ export declare class OakUniqueViolationException extends OakException { attrs: string[]; }>, message?: string); } -export declare class OakImportDataParseException extends OakException { +export declare class OakImportDataParseException extends OakException { line: number; header?: string; constructor(message: string, line: number, header?: string); } -export declare class OakOperExistedException extends OakDataException { +export declare class OakOperExistedException extends OakDataException { } -export declare class OakRowUnexistedException extends OakDataException { +export declare class OakRowUnexistedException extends OakDataException { private rows; constructor(rows: Array<{ entity: any; @@ -36,22 +39,20 @@ export declare class OakRowUnexistedException extends OakDataException { } export declare class OakExternalException extends Error { } -export declare class OakUserException extends OakException { +export declare class OakUserException extends OakException { } /** * 数据不一致异常,系统认为现有的数据不允许相应的动作时抛此异常 * */ -export declare class OakRowInconsistencyException extends OakUserException { - private data?; +export declare class OakRowInconsistencyException extends OakUserException { constructor(data?: OpRecord, message?: string); - getData(): OpRecord | undefined; toString(): string; } /** * 当输入的数据非法时抛此异常,attributes表示非法的属性 */ -export declare class OakInputIllegalException extends OakUserException { +export declare class OakInputIllegalException extends OakUserException { private attributes; private entity; constructor(entity: string, attributes: string[], message?: string); @@ -63,24 +64,24 @@ export declare class OakInputIllegalException extends OakUserException { /** * 用户权限不够时抛的异常 */ -export declare class OakUserUnpermittedException extends OakUserException { +export declare class OakUserUnpermittedException extends OakUserException { } /** * 用户未登录抛的异常 */ -export declare class OakUnloggedInException extends OakUserException { +export declare class OakUnloggedInException extends OakUserException { constructor(message?: string); } /** * 用户未登录抛的异常 */ -export declare class OakRowLockedException extends OakUserException { +export declare class OakRowLockedException extends OakUserException { constructor(message?: string); } /** * 要插入行时,发现已经有相同的行数据 */ -export declare class OakCongruentRowExists extends OakUserException { +export declare class OakCongruentRowExists extends OakUserException { private data; private entity; constructor(entity: T, data: ED[T]['OpSchema'], message?: string); @@ -88,11 +89,12 @@ export declare class OakCongruentRowExists extends OakUserException { constructor(message?: string | undefined); } -export declare function makeException(data: { +export declare function makeException(data: { name: string; message?: string; + opRecords: SelectOpResult; [A: string]: any; -}): OakException | OakExternalException | undefined; +}): OakException | undefined; diff --git a/lib/types/Exception.js b/lib/types/Exception.js index f8d492d..658b020 100644 --- a/lib/types/Exception.js +++ b/lib/types/Exception.js @@ -2,6 +2,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.makeException = exports.OakDeadlock = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakRowUnexistedException = exports.OakOperExistedException = exports.OakImportDataParseException = exports.OakUniqueViolationException = exports.OakDataException = exports.OakException = void 0; var tslib_1 = require("tslib"); +var assert_1 = tslib_1.__importDefault(require("assert")); var OakException = /** @class */ (function (_super) { tslib_1.__extends(OakException, _super); function OakException(message) { @@ -17,12 +18,36 @@ var OakException = /** @class */ (function (_super) { else { _this.__proto__ = _newTarget.prototype; } + _this.opRecord = { + a: 's', + d: {}, + }; return _this; } + OakException.prototype.addData = function (entity, rows) { + var d = this.opRecord.d; + var addSingleRow = function (rowRoot, row) { + var id = row.id; + if (rowRoot[id]) { + Object.assign(rowRoot[id], row); + } + else { + rowRoot[id] = row; + } + }; + if (!d[entity]) { + d[entity] = {}; + } + rows.forEach(function (row) { return addSingleRow(d[entity], row); }); + }; + OakException.prototype.setOpRecords = function (opRecord) { + this.opRecord = opRecord; + }; OakException.prototype.toString = function () { return JSON.stringify({ name: this.constructor.name, message: this.message, + opRecord: this.opRecord, }); }; return OakException; @@ -109,17 +134,13 @@ var OakRowInconsistencyException = /** @class */ (function (_super) { tslib_1.__extends(OakRowInconsistencyException, _super); function OakRowInconsistencyException(data, message) { var _this = _super.call(this, message) || this; - _this.data = data; + (0, assert_1.default)(!data, '现在使用addData接口来传数据'); return _this; } - OakRowInconsistencyException.prototype.getData = function () { - return this.data; - }; OakRowInconsistencyException.prototype.toString = function () { return JSON.stringify({ name: this.constructor.name, message: this.message, - data: this.data, }); }; return OakRowInconsistencyException; @@ -235,46 +256,69 @@ function makeException(data) { var name = data.name; switch (name) { case 'OakException': { - return new OakException(data.message); + var e = new OakException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUserException': { - return new OakUserException(data.message); - } - case 'OakExternalException': { - return new OakExternalException(data.message); + var e = new OakUserException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakRowInconsistencyException': { - return new OakRowInconsistencyException(data.data, data.message); + var e = new OakRowInconsistencyException(data.data, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakInputIllegalException': { - return new OakInputIllegalException(data.entity, data.attributes, data.message); + var e = new OakInputIllegalException(data.entity, data.attributes, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUserUnpermittedException': { - return new OakUserUnpermittedException(data.message); + var e = new OakUserUnpermittedException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUnloggedInException': { - return new OakUnloggedInException(data.message); + var e = new OakUnloggedInException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakCongruentRowExists': { - return new OakCongruentRowExists(data.entity, data.data, data.message); + var e = new OakCongruentRowExists(data.entity, data.data, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakRowLockedException': { - return new OakRowLockedException(data.message); + var e = new OakRowLockedException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakRowUnexistedException': { - return new OakRowUnexistedException(data.rows); + var e = new OakRowUnexistedException(data.rows); + e.setOpRecords(data.opRecords); + return e; } case 'OakDeadlock': { - return new OakDeadlock(data.message); + var e = new OakDeadlock(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakDataException': { - return new OakDataException(data.message); + var e = new OakDataException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUniqueViolationException': { - return new OakUniqueViolationException(data.rows, data.message); + var e = new OakUniqueViolationException(data.rows, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakImportDataParseException': { - return new OakImportDataParseException(data.message, data.line, data.header); + var e = new OakImportDataParseException(data.message, data.line, data.header); + e.setOpRecords(data.opRecords); + return e; } default: return; diff --git a/lib/utils/SimpleConnector.d.ts b/lib/utils/SimpleConnector.d.ts index 9f3bba1..bcdc28b 100644 --- a/lib/utils/SimpleConnector.d.ts +++ b/lib/utils/SimpleConnector.d.ts @@ -8,7 +8,7 @@ export declare class SimpleConnector OakException, contextBuilder: (str: string | undefined) => (store: AsyncRowStore) => Promise); + constructor(serverUrl: string, makeException: (exceptionData: any) => OakException, contextBuilder: (str: string | undefined) => (store: AsyncRowStore) => Promise); callAspect(name: string, params: any, context: FrontCxt): Promise<{ result: any; opRecords: OpRecord[]; @@ -23,7 +23,7 @@ export declare class SimpleConnector | undefined; }; - serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { + serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record | undefined; }; diff --git a/package.json b/package.json index 3e3179f..9c8591e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.5.2", + "version": "2.6.1", "author": { "name": "XuChang" }, diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index 6f62d87..c38510f 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -16,6 +16,7 @@ import { getRelevantIds } from "./filter"; import { CreateOperation as CreateOperOperation } 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 { @@ -1311,6 +1312,7 @@ export abstract class CascadeStore exten operation: ED[T]['Operation'], context: Cxt, option: OP): OperationResult { + reinforceOperation(this.getSchema(), entity, operation); const { action, data, filter, id } = operation; let opData: any; const wholeBeforeFns: Array<() => any> = []; @@ -1375,6 +1377,7 @@ export abstract class CascadeStore exten operation: ED[T]['Operation'], context: Cxt, option: OP): Promise> { + reinforceOperation(this.getSchema(), entity, operation); const { action, data, filter, id } = operation; let opData: any; const wholeBeforeFns: Array<() => Promise> = []; @@ -1437,6 +1440,7 @@ export abstract class CascadeStore exten selection: ED[T]['Selection'], context: Cxt, option: OP): Partial[] { + reinforceSelection(this.getSchema(), entity, selection); const { data, filter, indexFrom, count, sorter } = selection; const { projection, cascadeSelectionFns } = this.destructCascadeSelect( entity, @@ -1576,6 +1580,7 @@ export abstract class CascadeStore exten selection: ED[T]['Selection'], context: Cxt, option: OP): Promise[]> { + reinforceSelection(this.getSchema(), entity, selection); const { data, filter, indexFrom, count, sorter } = selection; const { projection, cascadeSelectionFns } = this.destructCascadeSelect( entity, diff --git a/src/store/checker.ts b/src/store/checker.ts index 2014f41..90224c7 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -55,19 +55,10 @@ export function translateCheckerInAsyncContext< dontCollect: true, blockTrigger: true, }); - const data = {}; - rows2.forEach( - ele => Object.assign(data, { - [ele.id as string]: ele, - }) - ); - throw new OakRowInconsistencyException({ - a: 's', - d: { - [entity2]: data, - } - }, errMsg); + const e = new OakRowInconsistencyException(undefined, errMsg); + e.addData(entity2, rows2); + throw e; } else { const rows2 = await context.select(entity, { @@ -79,19 +70,10 @@ export function translateCheckerInAsyncContext< dontCollect: true, blockTrigger: true, }); - const data = {}; - rows2.forEach( - ele => Object.assign(data, { - [ele.id as string]: ele, - }) - ); - throw new OakRowInconsistencyException({ - a: 's', - d: { - [entity]: data, - } - }, errMsg); + const e = new OakRowInconsistencyException(undefined, errMsg); + e.addData(entity, rows2); + throw e; } } }) as UpdateTriggerInTxn['fn']; @@ -191,7 +173,8 @@ export function translateCheckerInSyncContext< if (checkFilterContains(entity, context, filter2, operationFilter, true)) { return; } - throw new OakRowInconsistencyException(undefined, errMsg); + const e = new OakRowInconsistencyException(undefined, errMsg); + throw e; } }; return { @@ -568,15 +551,9 @@ export function createRemoveCheckers { if (row) { - const record = { - a: 's', - d: { - [e]: { - [row.id!]: row, - } - } - } as SelectOpResult; - throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${e as string}」关联的行`); + const err = new OakRowInconsistencyException(undefined, `您无法删除存在有效数据「${e as string}」关联的行`); + err.addData(e, [row]); + throw err; } } ) @@ -585,15 +562,9 @@ export function createRemoveCheckers; - throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${e as string}」关联的行`); + const err = new OakRowInconsistencyException(undefined, `您无法删除存在有效数据「${e as string}」关联的行`); + err.addData(e, [row]); + throw err; } } } @@ -619,15 +590,9 @@ export function createRemoveCheckers { if (row) { - const record = { - a: 's', - d: { - [otm]: { - [row.id!]: row, - } - } - } as SelectOpResult; - throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${otm as string}」关联的行`); + const e = new OakRowInconsistencyException(undefined, `您无法删除存在有效数据「${otm as string}」关联的行`); + e.addData(otm, [row]); + throw e; } } ) @@ -644,7 +609,9 @@ export function createRemoveCheckers; - throw new OakRowInconsistencyException(record, `您无法删除存在有效数据「${otm as string}」关联的行`); + const e = new OakRowInconsistencyException(undefined, `您无法删除存在有效数据「${otm as string}」关联的行`); + e.addData(otm, [row]); + throw e; } } } diff --git a/src/store/selection.ts b/src/store/selection.ts index ca62d1b..41f9e21 100644 --- a/src/store/selection.ts +++ b/src/store/selection.ts @@ -5,12 +5,39 @@ 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; + Object.assign(data, { + '$$createAt$$': 1, + }); // 有的页面依赖于其它页面取数据,有时两个页面的filter的差异会导致有一个加createAt,有一个不加,此时可能产生前台取数据不完整的异常。先统一加上 const checkNode = (projectionNode: ED[keyof ED]['Selection']['data'], attrs: string[]) => { attrs.forEach( @@ -232,4 +259,20 @@ export function reinforceSelection(schema: StorageSchema< $$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/types/Auth.ts b/src/types/Auth.ts index 27db51f..cee1553 100644 --- a/src/types/Auth.ts +++ b/src/types/Auth.ts @@ -89,7 +89,7 @@ export type AuthDef = { relationAuth?: CascadeRelationAuth>; actionAuth?: CascadeActionAuth; cascadeRemove?: { - [E in keyof ED[T]['OpSchema'] | '@entity']?: ActionOnRemove; + [E in (keyof ED | '@entity')]?: ActionOnRemove; } }; diff --git a/src/types/Connector.ts b/src/types/Connector.ts index ea47835..9876113 100644 --- a/src/types/Connector.ts +++ b/src/types/Connector.ts @@ -20,7 +20,7 @@ export abstract class Connector; }; - abstract serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { + abstract serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record; }; diff --git a/src/types/Entity.ts b/src/types/Entity.ts index 47e6712..a1fb534 100644 --- a/src/types/Entity.ts +++ b/src/types/Entity.ts @@ -216,7 +216,7 @@ export type SelectOpResult = { a: 's', d: { [T in keyof ED]?: { - [ID: string]: ED[T]['OpSchema']; + [ID: string]: Partial; }; }; } diff --git a/src/types/Exception.ts b/src/types/Exception.ts index d40427a..289b87b 100644 --- a/src/types/Exception.ts +++ b/src/types/Exception.ts @@ -1,6 +1,8 @@ -import { EntityDict, OpRecord } from "./Entity"; +import assert from "assert"; +import { EntityDict, OpRecord, SelectOpResult } from "./Entity"; -export class OakException extends Error { +export class OakException extends Error { + opRecord: SelectOpResult; constructor(message?: string) { super(message); this.name = new.target.name; @@ -12,21 +14,49 @@ export class OakException extends Error { } else { (this as any).__proto__ = new.target.prototype; } + this.opRecord = { + a: 's', + d: {}, + }; + } + + addData(entity: T, rows: Partial[]) { + const { d } = this.opRecord; + const addSingleRow = (rowRoot: Record>, row: Partial) => { + const { id } = row; + if (rowRoot[id!]) { + Object.assign(rowRoot[id!], row); + } + else { + rowRoot[id!] = row; + } + }; + if (!d[entity]) { + d[entity] = {}; + } + rows.forEach( + row => addSingleRow(d[entity]!, row) + ); + } + + setOpRecords(opRecord: SelectOpResult) { + this.opRecord = opRecord; } toString() { return JSON.stringify({ name: this.constructor.name, message: this.message, + opRecord: this.opRecord, }); } } -export class OakDataException extends OakException { +export class OakDataException extends OakException { // 表示由数据层发现的异常 } -export class OakUniqueViolationException extends OakException { +export class OakUniqueViolationException extends OakException { rows: Array<{ id?: string; attrs: string[]; @@ -40,7 +70,7 @@ export class OakUniqueViolationException extends OakException { } } -export class OakImportDataParseException extends OakException { +export class OakImportDataParseException extends OakException { line: number; header?: string; @@ -52,11 +82,11 @@ export class OakImportDataParseException extends OakException { } } -export class OakOperExistedException extends OakDataException { +export class OakOperExistedException extends OakDataException { // 进行操作时发现同样id的Oper对象已经存在 } -export class OakRowUnexistedException extends OakDataException { +export class OakRowUnexistedException extends OakDataException { private rows: Array<{ entity: any; selection: any; @@ -80,7 +110,7 @@ export class OakExternalException extends Error { // 表示由oak生态外部造成的异常,比如网络中断 } -export class OakUserException extends OakException { +export class OakUserException extends OakException { // 继承了这个类的异常统一视为“可接受的、由用户操作造成的异常” }; @@ -89,22 +119,16 @@ export class OakUserException extends OakException { * 数据不一致异常,系统认为现有的数据不允许相应的动作时抛此异常 * */ -export class OakRowInconsistencyException extends OakUserException { - private data?: OpRecord; +export class OakRowInconsistencyException extends OakUserException { constructor(data?: OpRecord, message?: string) { super(message); - this.data = data; - } - - getData() { - return this.data; + assert(!data, '现在使用addData接口来传数据'); } toString(): string { return JSON.stringify({ name: this.constructor.name, message: this.message, - data: this.data, }); } }; @@ -112,7 +136,7 @@ export class OakRowInconsistencyException extends OakUser /** * 当输入的数据非法时抛此异常,attributes表示非法的属性 */ -export class OakInputIllegalException extends OakUserException { +export class OakInputIllegalException extends OakUserException { private attributes: string[]; private entity: string; constructor(entity: string, attributes: string[], message?: string) { @@ -148,14 +172,14 @@ export class OakInputIllegalException extends OakUserException { /** * 用户权限不够时抛的异常 */ -export class OakUserUnpermittedException extends OakUserException { +export class OakUserUnpermittedException extends OakUserException { }; /** * 用户未登录抛的异常 */ -export class OakUnloggedInException extends OakUserException { +export class OakUnloggedInException extends OakUserException { constructor(message?: string) { super(message || '您尚未登录'); } @@ -165,7 +189,7 @@ export class OakUnloggedInException extends OakUserException { /** * 用户未登录抛的异常 */ - export class OakRowLockedException extends OakUserException { + export class OakRowLockedException extends OakUserException { constructor(message?: string) { super(message || '该行数据正在被更新中,请稍后再试'); } @@ -173,7 +197,7 @@ export class OakUnloggedInException extends OakUserException { /** * 要插入行时,发现已经有相同的行数据 */ -export class OakCongruentRowExists extends OakUserException { +export class OakCongruentRowExists extends OakUserException { private data: ED[T]['OpSchema']; private entity: T; constructor(entity: T, data: ED[T]['OpSchema'], message?: string) { @@ -200,60 +224,84 @@ export class OakCongruentRowExists ex } } -export class OakDeadlock extends OakUserException { +export class OakDeadlock extends OakUserException { constructor(message?: string | undefined) { super(message || '发现死锁'); } }; -export function makeException(data: { +export function makeException(data: { name: string; message?: string; + opRecords: SelectOpResult; [A: string]: any; }) { const { name } = data; switch (name) { case 'OakException': { - return new OakException(data.message); + const e = new OakException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUserException': { - return new OakUserException(data.message); - } - case 'OakExternalException': { - return new OakExternalException(data.message); + const e = new OakUserException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakRowInconsistencyException': { - return new OakRowInconsistencyException(data.data, data.message); + const e = new OakRowInconsistencyException(data.data, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakInputIllegalException': { - return new OakInputIllegalException(data.entity, data.attributes, data.message); + const e = new OakInputIllegalException(data.entity, data.attributes, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUserUnpermittedException': { - return new OakUserUnpermittedException(data.message); + const e = new OakUserUnpermittedException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUnloggedInException': { - return new OakUnloggedInException(data.message); + const e = new OakUnloggedInException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakCongruentRowExists': { - return new OakCongruentRowExists(data.entity, data.data, data.message); + const e = new OakCongruentRowExists(data.entity, data.data, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakRowLockedException': { - return new OakRowLockedException(data.message); + const e = new OakRowLockedException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakRowUnexistedException': { - return new OakRowUnexistedException(data.rows); + const e = new OakRowUnexistedException(data.rows); + e.setOpRecords(data.opRecords); + return e; } case 'OakDeadlock': { - return new OakDeadlock(data.message); + const e = new OakDeadlock(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakDataException': { - return new OakDataException(data.message); + const e = new OakDataException(data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakUniqueViolationException': { - return new OakUniqueViolationException(data.rows, data.message); + const e = new OakUniqueViolationException(data.rows, data.message); + e.setOpRecords(data.opRecords); + return e; } case 'OakImportDataParseException': { - return new OakImportDataParseException(data.message!, data.line, data.header); + const e = new OakImportDataParseException(data.message!, data.line, data.header); + e.setOpRecords(data.opRecords); + return e; } default: return; diff --git a/src/utils/SimpleConnector.ts b/src/utils/SimpleConnector.ts index 7b5b5d2..ea20eeb 100644 --- a/src/utils/SimpleConnector.ts +++ b/src/utils/SimpleConnector.ts @@ -25,10 +25,10 @@ function makeContentTypeAndBody(data: any) { export class SimpleConnector, FrontCxt extends SyncContext> extends Connector { static ROUTER = '/aspect'; private serverUrl: string; - private makeException: (exceptionData: any) => OakException; + private makeException: (exceptionData: any) => OakException; private contextBuilder: (str: string | undefined) => (store: AsyncRowStore) => Promise; - constructor(serverUrl: string, makeException: (exceptionData: any) => OakException, contextBuilder: (str: string | undefined) => (store: AsyncRowStore) => Promise) { + constructor(serverUrl: string, makeException: (exceptionData: any) => OakException, contextBuilder: (str: string | undefined) => (store: AsyncRowStore) => Promise) { super(); this.serverUrl = `${serverUrl}${SimpleConnector.ROUTER}`; this.makeException = makeException; @@ -100,7 +100,7 @@ export class SimpleConnector | undefined; } { + serializeException(exception: OakException, headers: IncomingHttpHeaders, body: any): { body: any; headers?: Record | undefined; } { return { body: { exception: exception.toString(),