diff --git a/lib/store/AsyncRowStore.d.ts b/lib/store/AsyncRowStore.d.ts index 3998bf9..c5a0247 100644 --- a/lib/store/AsyncRowStore.d.ts +++ b/lib/store/AsyncRowStore.d.ts @@ -7,6 +7,7 @@ export declare abstract class AsyncContext implements Con opRecords: OpRecord[]; private scene?; private headers?; + private message?; events: { commit: Array<() => Promise>; rollback: Array<() => Promise>; @@ -32,6 +33,8 @@ export declare abstract class AsyncContext implements Con mergeMultipleResults(toBeMerged: OperationResult[]): OperationResult; getCurrentTxnId(): string | undefined; getSchema(): import("../types").StorageSchema; + setMessage(message: string): void; + getMessage(): string | undefined; abstract isRoot(): boolean; abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined; abstract toString(): string; diff --git a/lib/store/AsyncRowStore.js b/lib/store/AsyncRowStore.js index 97d800b..6bb86a7 100644 --- a/lib/store/AsyncRowStore.js +++ b/lib/store/AsyncRowStore.js @@ -176,6 +176,12 @@ var AsyncContext = /** @class */ (function () { AsyncContext.prototype.getSchema = function () { return this.rowStore.getSchema(); }; + AsyncContext.prototype.setMessage = function (message) { + this.message = message; + }; + AsyncContext.prototype.getMessage = function () { + return this.message; + }; return AsyncContext; }()); exports.AsyncContext = AsyncContext; diff --git a/lib/store/CascadeStore.js b/lib/store/CascadeStore.js index d886ec5..efdfc44 100644 --- a/lib/store/CascadeStore.js +++ b/lib/store/CascadeStore.js @@ -543,7 +543,7 @@ var CascadeStore = /** @class */ (function (_super) { var _g = tslib_1.__read(relation, 2), entityOtm_1 = _g[0], foreignKey_2 = _g[1]; var otmOperations = data[attr]; var dealWithOneToMany = function (otm) { - var _a, _b, _c, _d, _e; + var _a, _b, _c, _d, _e, _f; var actionOtm = otm.action, dataOtm = otm.data, filterOtm = otm.filter; if (!foreignKey_2) { // 基于entity/entityId的one-to-many @@ -581,13 +581,25 @@ var CascadeStore = /** @class */ (function (_super) { } } else { - // 这里先假设A(必是update)的filter上一定有id,否则用户界面上应该设计不出来这样的操作 - // 这个倒是好像不可能出现create/update的一对多,如果遇到了再完善 - Object.assign(otm, { - filter: (0, filter_1.addFilterSegment)((_a = {}, - _a[entity] = filter, - _a), filterOtm), - }); + // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 + // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)({ + entity: entity, + entityId: filter.id, + }, filterOtm), + }); + } + else { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)((_a = {}, + _a[entity] = filter, + _a), filterOtm), + }); + } + } if (action === 'remove' && actionOtm === 'update') { Object.assign(dataOtm, { entity: null, @@ -634,20 +646,34 @@ var CascadeStore = /** @class */ (function (_super) { } } else { - // update可能出现上层filter不是根据id的(userEntityGrant的过期触发的wechatQrCode的过期,见general中的userEntityGrant的trigger) - Object.assign(otm, { - filter: (0, filter_1.addFilterSegment)((_d = {}, - _d[foreignKey_2.slice(0, foreignKey_2.length - 2)] = filter, - _d), filterOtm), - }); + // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 + // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 + // 绝大多数情况都是id,但也有可能update可能出现上层filter不是根据id的(userEntityGrant的过期触发的wechatQrCode的过期,见general中的userEntityGrant的trigger) + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)((_d = {}, + _d[foreignKey_2] = filter.id, + _d), filterOtm), + }); + } + else { + Object.assign(otm, { + filter: (0, filter_1.addFilterSegment)((_e = {}, + _e[foreignKey_2.slice(0, foreignKey_2.length - 2)] = filter, + _e), filterOtm), + }); + } + } if (action === 'remove' && actionOtm === 'update') { - Object.assign(dataOtm, (_e = {}, - _e[foreignKey_2] = null, - _e)); + Object.assign(dataOtm, (_f = {}, + _f[foreignKey_2] = null, + _f)); } } } - beforeFns.push(function () { return cascadeUpdate.call(_this, entityOtm_1, otm, context, option2); }); + // 一对多的依赖应该后建,否则中间会出现空指针,导致checker等出错 + afterFns.push(function () { return cascadeUpdate.call(_this, entityOtm_1, otm, context, option2); }); }; if (otmOperations instanceof Array) { try { @@ -797,7 +823,7 @@ var CascadeStore = /** @class */ (function (_super) { context, option ); - + const row = data.find( ele => ele.id === congruentRow.id ); @@ -815,7 +841,7 @@ var CascadeStore = /** @class */ (function (_super) { context, option ); - + return result2 + result3; } else { diff --git a/lib/store/modi.js b/lib/store/modi.js index e912483..c7e53db 100644 --- a/lib/store/modi.js +++ b/lib/store/modi.js @@ -153,7 +153,8 @@ function createModiRelatedTriggers(schema) { var _loop_2 = function (entity) { var inModi = schema[entity].inModi; if (inModi) { - // 当关联modi的对象被删除时,对应的modi也删除 + // 当关联modi的对象被删除时,对应的modi也删除。这里似乎只需要删除掉活跃对象?因为oper不能删除,所以oper和modi是必须要支持对deleted对象的容错? + // 这里没有想清楚,by Xc 20230209 triggers.push({ name: "\u5F53\u5220\u9664".concat(entity, "\u5BF9\u8C61\u65F6\uFF0C\u5220\u9664\u76F8\u5173\u8054\u7684modi\u7684modiEntity"), action: 'remove', @@ -163,43 +164,42 @@ function createModiRelatedTriggers(schema) { fn: function (_a, context, option) { var operation = _a.operation; return tslib_1.__awaiter(_this, void 0, void 0, function () { - var data, id, _b, _c, _d, _e, _f, _g; - var _h, _j; - return tslib_1.__generator(this, function (_k) { - switch (_k.label) { + var filter, _b, _c, _d, _e, _f, _g; + var _h, _j, _k, _l; + return tslib_1.__generator(this, function (_m) { + switch (_m.label) { case 0: - data = operation.data; - id = data.id; + filter = operation.filter; _c = (_b = context).operate; _d = ['modiEntity']; _h = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; - case 1: return [4 /*yield*/, _c.apply(_b, _d.concat([(_h.id = _k.sent(), + case 1: return [4 /*yield*/, _c.apply(_b, _d.concat([(_h.id = _m.sent(), _h.action = 'remove', _h.data = {}, _h.filter = { - modi: { - entity: entity, - entityId: id, - }, + modi: (_j = {}, + _j[entity] = filter, + _j.iState = 'active', + _j), }, _h), { dontCollect: true }]))]; case 2: - _k.sent(); + _m.sent(); _f = (_e = context).operate; _g = ['modi']; - _j = {}; + _k = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; - case 3: return [4 /*yield*/, _f.apply(_e, _g.concat([(_j.id = _k.sent(), - _j.action = 'remove', - _j.data = {}, - _j.filter = { - entity: entity, - entityId: id, - }, - _j), { dontCollect: true }]))]; + case 3: return [4 /*yield*/, _f.apply(_e, _g.concat([(_k.id = _m.sent(), + _k.action = 'remove', + _k.data = {}, + _k.filter = (_l = {}, + _l[entity] = filter, + _l.iState = 'active', + _l), + _k), { dontCollect: true }]))]; case 4: - _k.sent(); + _m.sent(); return [2 /*return*/, 0]; } }); diff --git a/lib/store/selection.js b/lib/store/selection.js index f4588d6..cf9cc30 100644 --- a/lib/store/selection.js +++ b/lib/store/selection.js @@ -150,7 +150,7 @@ function reinforceSelection(schema, entity, selection) { var projectionNodeDict = {}; var checkProjectionNode = function (entity2, projectionNode) { var _a, _b, _c; - var necessaryAttrs = ['id', '$$createAt$$']; // 因有的页面依赖于其他页面的数据,因此默认的createAt的filter不一定会加上,为了保险起见在这里统一加上 + 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])); diff --git a/lib/types/Aspect.d.ts b/lib/types/Aspect.d.ts index a8ddc70..8f30761 100644 --- a/lib/types/Aspect.d.ts +++ b/lib/types/Aspect.d.ts @@ -7,6 +7,7 @@ export interface Aspect> { export interface AspectWrapper, AD extends Record>> { exec: (name: T, params: Parameters[0]) => Promise<{ result: Awaited>; - opRecords: OpRecord[]; + opRecords?: OpRecord[]; + message?: string | null; }>; } diff --git a/lib/types/Auth.d.ts b/lib/types/Auth.d.ts index 01ddf18..b4fb5f4 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 | '@entity')]?: ActionOnRemove; + [E in (keyof ED | keyof ED[T]['Schema'] | '@entity')]?: ActionOnRemove; }; }; export declare type AuthDefDict = { diff --git a/lib/types/Connector.d.ts b/lib/types/Connector.d.ts index f70c2e3..41e5786 100644 --- a/lib/types/Connector.d.ts +++ b/lib/types/Connector.d.ts @@ -7,7 +7,8 @@ import { OakException } from "./Exception"; export declare abstract class Connector, FrontCxt extends SyncContext> { abstract callAspect(name: string, params: any, context: FrontCxt): Promise<{ result: any; - opRecords: OpRecord[]; + opRecords?: OpRecord[]; + message?: string | null; }>; abstract getRouter(): string; abstract parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore): Promise<{ diff --git a/lib/utils/SimpleConnector.d.ts b/lib/utils/SimpleConnector.d.ts index bcdc28b..6d41251 100644 --- a/lib/utils/SimpleConnector.d.ts +++ b/lib/utils/SimpleConnector.d.ts @@ -2,7 +2,7 @@ import { IncomingHttpHeaders } from "http"; import { AsyncContext, AsyncRowStore } from '../store/AsyncRowStore'; import { SyncContext } from '../store/SyncRowStore'; -import { Connector, EntityDict, OakException, OpRecord } from "../types"; +import { Connector, EntityDict, OakException } from "../types"; export declare class SimpleConnector, FrontCxt extends SyncContext> extends Connector { static ROUTER: string; private serverUrl; @@ -11,7 +11,12 @@ export declare class SimpleConnector OakException, contextBuilder: (str: string | undefined) => (store: AsyncRowStore) => Promise); callAspect(name: string, params: any, context: FrontCxt): Promise<{ result: any; - opRecords: OpRecord[]; + opRecords: any; + message: string | null; + } | { + result: ArrayBuffer; + message: string | null; + opRecords?: undefined; }>; getRouter(): string; parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore): Promise<{ diff --git a/lib/utils/SimpleConnector.js b/lib/utils/SimpleConnector.js index 2edaf07..3809ea4 100644 --- a/lib/utils/SimpleConnector.js +++ b/lib/utils/SimpleConnector.js @@ -31,7 +31,7 @@ var SimpleConnector = /** @class */ (function (_super) { } SimpleConnector.prototype.callAspect = function (name, params, context) { return tslib_1.__awaiter(this, void 0, void 0, function () { - var cxtStr, _a, contentType, body, response, err, _b, exception, result, opRecords; + var cxtStr, _a, contentType, body, response, err, message, responseType, _b, exception, result, opRecords, result; return tslib_1.__generator(this, function (_c) { switch (_c.label) { case 0: @@ -52,6 +52,9 @@ var SimpleConnector = /** @class */ (function (_super) { err = new types_1.OakExternalException("\u7F51\u7EDC\u8BF7\u6C42\u8FD4\u56DE\u5F02\u5E38\uFF0Cstatus\u662F".concat(response.status)); throw err; } + message = response.headers.get('oak-message'); + responseType = response.headers.get('Content-Type'); + if (!(responseType === null || responseType === void 0 ? void 0 : responseType.toLocaleLowerCase().match(/application\/json/i))) return [3 /*break*/, 3]; return [4 /*yield*/, response.json()]; case 2: _b = _c.sent(), exception = _b.exception, result = _b.result, opRecords = _b.opRecords; @@ -61,7 +64,18 @@ var SimpleConnector = /** @class */ (function (_super) { return [2 /*return*/, { result: result, opRecords: opRecords, + message: message, }]; + case 3: + if (!(responseType === null || responseType === void 0 ? void 0 : responseType.toLocaleLowerCase().match(/application\/octet-stream/i))) return [3 /*break*/, 5]; + return [4 /*yield*/, response.arrayBuffer()]; + case 4: + result = _c.sent(); + return [2 /*return*/, { + result: result, + message: message, + }]; + case 5: throw new Error("\u5C1A\u4E0D\u652F\u6301\u7684content-type\u7C7B\u578B".concat(responseType)); } }); }); @@ -102,6 +116,9 @@ var SimpleConnector = /** @class */ (function (_super) { result: result, opRecords: context.opRecords, }, + headers: { + 'oak-message': context.getMessage(), + }, }; }; SimpleConnector.prototype.serializeException = function (exception, headers, body) { diff --git a/lib/utils/url.d.ts b/lib/utils/url.d.ts new file mode 100644 index 0000000..018e2ad --- /dev/null +++ b/lib/utils/url.d.ts @@ -0,0 +1 @@ +export declare function composeUrl(url: string, params: Record): string; diff --git a/lib/utils/url.js b/lib/utils/url.js new file mode 100644 index 0000000..6bcfe0b --- /dev/null +++ b/lib/utils/url.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.composeUrl = void 0; +var url_1 = require("url"); +function composeUrl(url, params) { + var urlSp = new url_1.URLSearchParams(params); + if (url.includes('?')) { + return "".concat(url, "&").concat(urlSp); + } + return "".concat(url, "?").concat(urlSp); +} +exports.composeUrl = composeUrl; diff --git a/package.json b/package.json index 9c8591e..3349451 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.6.1", + "version": "2.6.2", "author": { "name": "XuChang" }, diff --git a/src/store/AsyncRowStore.ts b/src/store/AsyncRowStore.ts index 5aaa1ad..5cc9835 100644 --- a/src/store/AsyncRowStore.ts +++ b/src/store/AsyncRowStore.ts @@ -9,6 +9,7 @@ export abstract class AsyncContext implements Context { opRecords: OpRecord[]; private scene?: string; private headers?: IncomingHttpHeaders; + private message?: string; events: { commit: Array<() => Promise>; rollback: Array<() => Promise>; @@ -128,6 +129,14 @@ export abstract class AsyncContext implements Context { return this.rowStore.getSchema(); } + setMessage(message: string) { + this.message = message; + } + + getMessage() { + return this.message; + } + abstract isRoot(): boolean; abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined; diff --git a/src/store/CascadeStore.ts b/src/store/CascadeStore.ts index c38510f..a5c19f3 100644 --- a/src/store/CascadeStore.ts +++ b/src/store/CascadeStore.ts @@ -327,7 +327,7 @@ export abstract class CascadeStore exten cascadeSelectionFns.push( (result) => { const aggrResults = result.map( - (row) => { + (row) => { const aggrResult = aggregateFn.call(this, entity2, { data: subProjection, filter: combineFilters([{ @@ -411,7 +411,7 @@ export abstract class CascadeStore exten cascadeSelectionFns.push( (result) => { const aggrResults = result.map( - (row) => { + (row) => { const aggrResult = aggregateFn.call(this, entity2, { data: subProjection, filter: combineFilters([{ @@ -464,7 +464,7 @@ export abstract class CascadeStore exten } ); }; - + if (ids.length > 0) { const subRows = cascadeSelectFn.call(this, entity2, { data: subProjection, @@ -726,13 +726,25 @@ export abstract class CascadeStore exten } } else { - // 这里先假设A(必是update)的filter上一定有id,否则用户界面上应该设计不出来这样的操作 - // 这个倒是好像不可能出现create/update的一对多,如果遇到了再完善 - Object.assign(otm, { - filter: addFilterSegment({ - [entity]: filter, - }, filterOtm), - }); + // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 + // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: addFilterSegment({ + entity, + entityId: filter.id, + }, filterOtm), + }); + } + else { + Object.assign(otm, { + filter: addFilterSegment({ + [entity]: filter, + }, filterOtm), + }); + } + } if (action === 'remove' && actionOtm === 'update') { Object.assign(dataOtm, { entity: null, @@ -777,83 +789,97 @@ export abstract class CascadeStore exten } } else { - // update可能出现上层filter不是根据id的(userEntityGrant的过期触发的wechatQrCode的过期,见general中的userEntityGrant的trigger) - Object.assign(otm, { - filter: addFilterSegment({ - [foreignKey.slice(0, foreignKey.length - 2)]: filter, - }, filterOtm), - }); - if (action === 'remove' && actionOtm === 'update') { - Object.assign(dataOtm, { - [foreignKey]: null, + // 这里优化一下,如果filter上有id,直接更新成根据entityId来过滤 + // 除了性能原因之外,还因为会制造出user: { id: xxx }这样的查询,general中不允许这样查询的出现 + // 绝大多数情况都是id,但也有可能update可能出现上层filter不是根据id的(userEntityGrant的过期触发的wechatQrCode的过期,见general中的userEntityGrant的trigger) + if (filter) { + if (filter.id && Object.keys(filter).length === 1) { + Object.assign(otm, { + filter: addFilterSegment({ + [foreignKey]: filter.id, + }, filterOtm), + }); + } + else { + Object.assign(otm, { + filter: addFilterSegment({ + [foreignKey.slice(0, foreignKey.length - 2)]: filter, + }, filterOtm), }); } } - } - - beforeFns.push(() => cascadeUpdate.call(this, entityOtm!, otm, context, option2)); - }; - - if (otmOperations instanceof Array) { - for (const oper of otmOperations) { - dealWithOneToMany(oper); + if (action === 'remove' && actionOtm === 'update') { + Object.assign(dataOtm, { + [foreignKey]: null, + }); + } } } - else { - dealWithOneToMany(otmOperations); + + // 一对多的依赖应该后建,否则中间会出现空指针,导致checker等出错 + afterFns.push(() => cascadeUpdate.call(this, entityOtm!, otm, context, option2)); + }; + + if (otmOperations instanceof Array) { + for (const oper of otmOperations) { + dealWithOneToMany(oper); } } + else { + dealWithOneToMany(otmOperations); + } } + } return { - data: opData, - beforeFns, - afterFns, - }; + data: opData, + beforeFns, + afterFns, +}; } // 对插入的数据,没有初始值的属性置null protected preProcessDataCreated(entity: T, data: ED[T]['Create']['data']) { - const now = Date.now(); - const { attributes } = this.getSchema()[entity]; - const processSingle = (data2: ED[T]['CreateSingle']['data']) => { - for (const key in attributes) { - if (data2[key] === undefined) { - Object.assign(data2, { - [key]: null, - }); - } + const now = Date.now(); + const { attributes } = this.getSchema()[entity]; + const processSingle = (data2: ED[T]['CreateSingle']['data']) => { + for (const key in attributes) { + if (data2[key] === undefined) { + Object.assign(data2, { + [key]: null, + }); } - Object.assign(data2, { - [CreateAtAttribute]: now, - [UpdateAtAttribute]: now, - [DeleteAtAttribute]: null, - }); - } - if (data instanceof Array) { - data.forEach( - ele => processSingle(ele) - ); - } - else { - processSingle(data as ED[T]['CreateSingle']['data']); } + Object.assign(data2, { + [CreateAtAttribute]: now, + [UpdateAtAttribute]: now, + [DeleteAtAttribute]: null, + }); } + if (data instanceof Array) { + data.forEach( + ele => processSingle(ele) + ); + } + else { + processSingle(data as ED[T]['CreateSingle']['data']); + } +} // 对更新的数据,去掉所有的undefined属性 protected preProcessDataUpdated(data: Record) { - const undefinedKeys = Object.keys(data).filter( - ele => data[ele] === undefined - ); - undefinedKeys.forEach( - ele => unset(data, ele) - ); - } + const undefinedKeys = Object.keys(data).filter( + ele => data[ele] === undefined + ); + undefinedKeys.forEach( + ele => unset(data, ele) + ); +} - judgeRelation(entity: keyof ED, attr: string) { - return judgeRelation(this.storageSchema, entity, attr); - } +judgeRelation(entity: keyof ED, attr: string) { + return judgeRelation(this.storageSchema, entity, attr); +} /** * 和具体的update过程无关的例程放在这里,包括对later动作的处理、对oper的记录以及对record的收集等 @@ -863,396 +889,45 @@ export abstract class CascadeStore exten * @param option */ private async doUpdateSingleRowAsync>(entity: T, - operation: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], - context: Cxt, - option: OP - ) { - const { data, action, id: operId, filter } = operation; - const now = Date.now(); + operation: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], + context: Cxt, + option: OP +) { + const { data, action, id: operId, filter } = operation; + const now = Date.now(); - switch (action) { - case 'create': { - this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); - if (option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity as string)) { - // 变成对modi的插入 - assert(option.modiParentId); - const modiCreate: CreateModiOperation = { - id: 'dummy', - action: 'create', - data: { - id: operId!, - targetEntity: entity as string, - action, - entity: option.modiParentEntity!, - entityId: option.modiParentId!, - filter: { - id: { - $in: [(data as any).id as string], //这里记录这个filter是为了后面update的时候直接在其上面update,参见本函数后半段关于modiUpsert相关的优化 - }, + switch (action) { + case 'create': { + this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); + if (option.modiParentEntity && !['modi', 'modiEntity', 'oper', 'operEntity'].includes(entity as string)) { + // 变成对modi的插入 + assert(option.modiParentId); + const modiCreate: CreateModiOperation = { + id: 'dummy', + action: 'create', + data: { + id: operId!, + targetEntity: entity as string, + action, + entity: option.modiParentEntity!, + entityId: option.modiParentId!, + filter: { + id: { + $in: [(data as any).id as string], //这里记录这个filter是为了后面update的时候直接在其上面update,参见本函数后半段关于modiUpsert相关的优化 }, - data, - iState: 'active', }, - }; - await this.cascadeUpdateAsync('modi', modiCreate, context, option); - return 1; - } - else { - let result = 0; - const createInner = async (operation2: ED[T]['Create']) => { - try { - result += await this.updateAbjointRowAsync( - entity, - operation2, - context, - option - ); - } - catch (e: any) { - /* 这段代码是处理插入时有重复的行,现在看有问题,等实际需求出现再写 - if (e instanceof OakCongruentRowExists) { - if (option.allowExists) { - // 如果允许存在,对已存在行进行update,剩下的行继续insert - const congruentRow = e.getData() as ED[T]['OpSchema']; - if (data instanceof Array) { - const rest = data.filter( - ele => ele.id !== congruentRow.id - ); - if (rest.length === data.length) { - throw e; - } - const result2 = await this.updateAbjointRow( - entity, - Object.assign({}, operation, { - data: rest, - }), - context, - option - ); - - const row = data.find( - ele => ele.id === congruentRow.id - ); - const updateData = omit(row, ['id', '$$createAt$$']); - const result3 = await this.updateAbjointRow( - entity, - { - id: await generateNewId(), - action: 'update', - data: updateData, - filter: { - id: congruentRow.id, - } as any, - }, - context, - option - ); - - return result2 + result3; - } - else { - if (data.id !== congruentRow.id) { - throw e; - } - const updateData = omit(data, ['id', '$$createAt$$']); - const result2 = await this.updateAbjointRow( - entity, - { - id: await generateNewId(), - action: 'update', - data: updateData, - filter: { - id: congruentRow.id, - } as any, - }, - context, - option - ); - return result2; - } - } - } */ - throw e; - } - }; - - if (data instanceof Array) { - const multipleCreate = this.supportMultipleCreate(); - if (multipleCreate) { - await createInner(operation as ED[T]['Create']); - } - else { - for (const d of data) { - const createSingleOper: ED[T]['CreateSingle'] = { - id: 'any', - action: 'create', - data: d, - }; - await createInner(createSingleOper); - } - } - } - else { - await createInner(operation as ED[T]['Create']); - } - - if (!option.dontCollect) { - context.opRecords.push({ - a: 'c', - e: entity, - d: data as ED[T]['OpSchema'] | ED[T]['OpSchema'][], - }); - } - if (!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string)) { - // 按照框架要求生成Oper和OperEntity这两个内置的对象 - assert(operId); - const operatorId = await context.getCurrentUserId(true); - if (operatorId) { - const createOper: CreateOperOperation = { - id: 'dummy', - action: 'create', - data: { - id: operId, - action, - data, - operatorId, - operEntity$oper: data instanceof Array ? { - id: 'dummy', - action: 'create', - data: await Promise.all( - data.map( - async (ele) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: ele.id, - }) - ) - ), - } : [{ - id: 'dummy', - action: 'create', - data: { - id: await generateNewIdAsync(), - entity: entity as string, - entityId: (data as ED[T]['CreateSingle']['data']).id, - }, - }] - }, - }; - await this.cascadeUpdateAsync('oper', createOper, context, { - dontCollect: true, - dontCreateOper: true, - }); - } - } - return result!; - } + data, + iState: 'active', + }, + }; + await this.cascadeUpdateAsync('modi', modiCreate, context, option); + return 1; } - default: { - // 这里要优化一下,显式的对id的update/remove不要去查了,节省数据库层的性能(如果这些row是建立在一个create的modi上也查不到) - const ids = getRelevantIds(filter); - if (ids.length === 0) { - const selection: ED[T]['Selection'] = { - data: { - id: 1, - }, - filter: operation.filter, - indexFrom: operation.indexFrom, - count: operation.count, - }; - const rows = await this.selectAbjointRowAsync(entity, selection, context, { - dontCollect: true, - }); - ids.push(...(rows.map(ele => ele.id! as string))); - } - if (data) { - this.preProcessDataUpdated(data); - } - - if (option.modiParentEntity && !['modi', 'modiEntity'].includes(entity as string)) { - // 延时更新,变成对modi的插入 - // 变成对modi的插入 - // 优化,这里如果是对同一个targetEntity反复update,则变成对最后一条create/update的modi进行update,以避免发布文章这样的需求时产生过多的modi - let modiUpsert: CreateModiOperation | UpdateModiOperation | undefined; - if (action !== 'remove') { - const upsertModis = await this.selectAbjointRowAsync('modi', { - data: { - id: 1, - data: 1, - }, - filter: { - targetEntity: entity as string, - action: { - $in: ['create', 'update'], - }, - entity: option.modiParentEntity!, - entityId: option.modiParentId!, - iState: 'active', - filter: ids.length > 0 ? { - id: { - $in: ids, - }, - } : filter, - }, - sorter: [ - { - $attr: { - $$createAt$$: 1, - }, - $direction: 'desc', - } - ], - indexFrom: 0, - count: 1, - }, context, option); - if (upsertModis.length > 0) { - const { data: originData, id: originId } = upsertModis[0]; - modiUpsert = { - id: 'dummy', - action: 'update', - data: { - data: Object.assign({}, originData, data), - }, - filter: { - id: originId as string, - } - }; - } - } - if (!modiUpsert) { - modiUpsert = { - id: 'dummy', - action: 'create', - data: { - id: operId, - targetEntity: entity as string, - entity: option.modiParentEntity!, - entityId: option.modiParentId!, - action, - data, - iState: 'active', - filter, - }, - }; - if (ids.length > 0){ - modiUpsert.data.modiEntity$modi = { - id: 'dummy', - action: 'create', - data: await Promise.all( - ids.map( - async (id) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: id, - }) - ) - ), - }; - } - } - await this.cascadeUpdateAsync('modi', modiUpsert!, context, option); - return 1; - } - else { - const createOper = async () => { - if (!option?.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string) && ids.length > 0) { - // 按照框架要求生成Oper和OperEntity这两个内置的对象 - assert(operId); - const createOper: CreateOperOperation = { - id: 'dummy', - action: 'create', - data: { - id: operId, - action, - data, - operEntity$oper: { - id: 'dummy', - action: 'create', - data: await Promise.all( - ids.map( - async (ele) => ({ - id: await generateNewIdAsync(), - entity: entity as string, - entityId: ele, - }) - ) - ) - }, - }, - } - await this.cascadeUpdateAsync('oper', createOper, context, { - dontCollect: true, - dontCreateOper: true, - }); - } - }; - if (action === 'remove') { - if (!option.dontCollect) { - context.opRecords.push({ - a: 'r', - e: entity, - f: { - id: { - $in: ids, - } - }, - }); - } - } - else { - const updateAttrCount = Object.keys(data).length; - if (updateAttrCount > 0) { - // 优化一下,如果不更新任何属性,则不实际执行 - Object.assign(data, { - $$updateAt$$: now, - }); - if (!option.dontCollect) { - context.opRecords.push({ - a: 'u', - e: entity, - d: data as ED[T]['Update']['data'], - f: { - id: { - $in: ids, - } - }, - }); - } - } - else if (action !== 'update') { - // 如果不是update动作而是用户自定义的动作,这里还是要记录oper - await createOper(); - return 0; - } - else { - return 0; - } - } - - const result = await this.updateAbjointRowAsync(entity, operation, context, option); - await createOper(); - - return result; - } - } - } - } - - private doUpdateSingleRow>(entity: T, - operation: ED[T]['Operation'], - context: Cxt, - option: OP - ) { - const { data, action, id: operId, filter } = operation; - const now = Date.now(); - - switch (action) { - case 'create': { - this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); + else { let result = 0; - const createInner = (operation2: ED[T]['Create']) => { + const createInner = async (operation2: ED[T]['Create']) => { try { - result += this.updateAbjointRow( + result += await this.updateAbjointRowAsync( entity, operation2, context, @@ -1260,6 +935,69 @@ export abstract class CascadeStore exten ); } catch (e: any) { + /* 这段代码是处理插入时有重复的行,现在看有问题,等实际需求出现再写 + if (e instanceof OakCongruentRowExists) { + if (option.allowExists) { + // 如果允许存在,对已存在行进行update,剩下的行继续insert + const congruentRow = e.getData() as ED[T]['OpSchema']; + if (data instanceof Array) { + const rest = data.filter( + ele => ele.id !== congruentRow.id + ); + if (rest.length === data.length) { + throw e; + } + const result2 = await this.updateAbjointRow( + entity, + Object.assign({}, operation, { + data: rest, + }), + context, + option + ); + + const row = data.find( + ele => ele.id === congruentRow.id + ); + const updateData = omit(row, ['id', '$$createAt$$']); + const result3 = await this.updateAbjointRow( + entity, + { + id: await generateNewId(), + action: 'update', + data: updateData, + filter: { + id: congruentRow.id, + } as any, + }, + context, + option + ); + + return result2 + result3; + } + else { + if (data.id !== congruentRow.id) { + throw e; + } + const updateData = omit(data, ['id', '$$createAt$$']); + const result2 = await this.updateAbjointRow( + entity, + { + id: await generateNewId(), + action: 'update', + data: updateData, + filter: { + id: congruentRow.id, + } as any, + }, + context, + option + ); + return result2; + } + } + } */ throw e; } }; @@ -1267,7 +1005,7 @@ export abstract class CascadeStore exten if (data instanceof Array) { const multipleCreate = this.supportMultipleCreate(); if (multipleCreate) { - createInner(operation as ED[T]['Create']); + await createInner(operation as ED[T]['Create']); } else { for (const d of data) { @@ -1276,17 +1014,216 @@ export abstract class CascadeStore exten action: 'create', data: d, }; - createInner(createSingleOper); + await createInner(createSingleOper); } } } else { - createInner(operation as ED[T]['Create']); + await createInner(operation as ED[T]['Create']); } - return result; + + if (!option.dontCollect) { + context.opRecords.push({ + a: 'c', + e: entity, + d: data as ED[T]['OpSchema'] | ED[T]['OpSchema'][], + }); + } + if (!option.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string)) { + // 按照框架要求生成Oper和OperEntity这两个内置的对象 + assert(operId); + const operatorId = await context.getCurrentUserId(true); + if (operatorId) { + const createOper: CreateOperOperation = { + id: 'dummy', + action: 'create', + data: { + id: operId, + action, + data, + operatorId, + operEntity$oper: data instanceof Array ? { + id: 'dummy', + action: 'create', + data: await Promise.all( + data.map( + async (ele) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: ele.id, + }) + ) + ), + } : [{ + id: 'dummy', + action: 'create', + data: { + id: await generateNewIdAsync(), + entity: entity as string, + entityId: (data as ED[T]['CreateSingle']['data']).id, + }, + }] + }, + }; + await this.cascadeUpdateAsync('oper', createOper, context, { + dontCollect: true, + dontCreateOper: true, + }); + } + } + return result!; } - default: { + } + default: { + // 这里要优化一下,显式的对id的update/remove不要去查了,节省数据库层的性能(如果这些row是建立在一个create的modi上也查不到) + const ids = getRelevantIds(filter); + if (ids.length === 0) { + const selection: ED[T]['Selection'] = { + data: { + id: 1, + }, + filter: operation.filter, + indexFrom: operation.indexFrom, + count: operation.count, + }; + const rows = await this.selectAbjointRowAsync(entity, selection, context, { + dontCollect: true, + }); + ids.push(...(rows.map(ele => ele.id! as string))); + } + if (data) { + this.preProcessDataUpdated(data); + } + + if (option.modiParentEntity && !['modi', 'modiEntity'].includes(entity as string)) { + // 延时更新,变成对modi的插入 + // 变成对modi的插入 + // 优化,这里如果是对同一个targetEntity反复update,则变成对最后一条create/update的modi进行update,以避免发布文章这样的需求时产生过多的modi + let modiUpsert: CreateModiOperation | UpdateModiOperation | undefined; + if (action !== 'remove') { + const upsertModis = await this.selectAbjointRowAsync('modi', { + data: { + id: 1, + data: 1, + }, + filter: { + targetEntity: entity as string, + action: { + $in: ['create', 'update'], + }, + entity: option.modiParentEntity!, + entityId: option.modiParentId!, + iState: 'active', + filter: ids.length > 0 ? { + id: { + $in: ids, + }, + } : filter, + }, + sorter: [ + { + $attr: { + $$createAt$$: 1, + }, + $direction: 'desc', + } + ], + indexFrom: 0, + count: 1, + }, context, option); + if (upsertModis.length > 0) { + const { data: originData, id: originId } = upsertModis[0]; + modiUpsert = { + id: 'dummy', + action: 'update', + data: { + data: Object.assign({}, originData, data), + }, + filter: { + id: originId as string, + } + }; + } + } + if (!modiUpsert) { + modiUpsert = { + id: 'dummy', + action: 'create', + data: { + id: operId, + targetEntity: entity as string, + entity: option.modiParentEntity!, + entityId: option.modiParentId!, + action, + data, + iState: 'active', + filter, + }, + }; + if (ids.length > 0) { + modiUpsert.data.modiEntity$modi = { + id: 'dummy', + action: 'create', + data: await Promise.all( + ids.map( + async (id) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: id, + }) + ) + ), + }; + } + } + await this.cascadeUpdateAsync('modi', modiUpsert!, context, option); + return 1; + } + else { + const createOper = async () => { + if (!option?.dontCreateOper && !['oper', 'operEntity', 'modiEntity', 'modi'].includes(entity as string) && ids.length > 0) { + // 按照框架要求生成Oper和OperEntity这两个内置的对象 + assert(operId); + const createOper: CreateOperOperation = { + id: 'dummy', + action: 'create', + data: { + id: operId, + action, + data, + operEntity$oper: { + id: 'dummy', + action: 'create', + data: await Promise.all( + ids.map( + async (ele) => ({ + id: await generateNewIdAsync(), + entity: entity as string, + entityId: ele, + }) + ) + ) + }, + }, + } + await this.cascadeUpdateAsync('oper', createOper, context, { + dontCollect: true, + dontCreateOper: true, + }); + } + }; if (action === 'remove') { + if (!option.dontCollect) { + context.opRecords.push({ + a: 'r', + e: entity, + f: { + id: { + $in: ids, + } + }, + }); + } } else { const updateAttrCount = Object.keys(data).length; @@ -1295,74 +1232,163 @@ export abstract class CascadeStore exten Object.assign(data, { $$updateAt$$: now, }); - this.preProcessDataUpdated(data); + if (!option.dontCollect) { + context.opRecords.push({ + a: 'u', + e: entity, + d: data as ED[T]['Update']['data'], + f: { + id: { + $in: ids, + } + }, + }); + } + } + else if (action !== 'update') { + // 如果不是update动作而是用户自定义的动作,这里还是要记录oper + await createOper(); + return 0; } else { return 0; } } - return this.updateAbjointRow(entity, operation, context, option); + const result = await this.updateAbjointRowAsync(entity, operation, context, option); + await createOper(); + + return result; } } } +} + + private doUpdateSingleRow>(entity: T, + operation: ED[T]['Operation'], + context: Cxt, + option: OP +) { + const { data, action, id: operId, filter } = operation; + const now = Date.now(); + + switch (action) { + case 'create': { + this.preProcessDataCreated(entity, data as ED[T]['Create']['data']); + let result = 0; + const createInner = (operation2: ED[T]['Create']) => { + try { + result += this.updateAbjointRow( + entity, + operation2, + context, + option + ); + } + catch (e: any) { + throw e; + } + }; + + if (data instanceof Array) { + const multipleCreate = this.supportMultipleCreate(); + if (multipleCreate) { + createInner(operation as ED[T]['Create']); + } + else { + for (const d of data) { + const createSingleOper: ED[T]['CreateSingle'] = { + id: 'any', + action: 'create', + data: d, + }; + createInner(createSingleOper); + } + } + } + else { + createInner(operation as ED[T]['Create']); + } + return result; + } + default: { + if (action === 'remove') { + } + else { + const updateAttrCount = Object.keys(data).length; + if (updateAttrCount > 0) { + // 优化一下,如果不更新任何属性,则不实际执行 + Object.assign(data, { + $$updateAt$$: now, + }); + this.preProcessDataUpdated(data); + } + else { + return 0; + } + } + + return this.updateAbjointRow(entity, operation, context, option); + } + } +} protected cascadeUpdate, OP extends OperateOption>( - entity: T, - operation: ED[T]['Operation'], - context: Cxt, - option: OP): OperationResult { + entity: T, + operation: ED[T]['Operation'], + context: Cxt, + option: OP): OperationResult < ED > { reinforceOperation(this.getSchema(), entity, operation); const { action, data, filter, id } = operation; let opData: any; - const wholeBeforeFns: Array<() => any> = []; - const wholeAfterFns: Array<() => any> = []; - const result: OperationResult = {}; + const wholeBeforeFns: Array<() => any> =[]; +const wholeAfterFns: Array<() => any> = []; +const result: OperationResult = {}; - if (['create', 'create-l'].includes(action) && data instanceof Array) { - opData = []; - for (const d of data) { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - d, - context, - option, - this.cascadeUpdate, - ); - opData.push(od); - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); - } - } - else { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - data, - context, - option, - this.cascadeUpdate, - filter - ); - opData = od; - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); - } +if (['create', 'create-l'].includes(action) && data instanceof Array) { + opData = []; + for (const d of data) { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + d, + context, + option, + this.cascadeUpdate, + ); + opData.push(od); + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); + } +} +else { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + data, + context, + option, + this.cascadeUpdate, + filter + ); + opData = od; + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); +} - const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = - Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { - data: opData as ED[T]['OpSchema'], - }); +const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = + Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { + data: opData as ED[T]['OpSchema'], + }); - for (const before of wholeBeforeFns) { - before(); - } - const count = this.doUpdateSingleRow(entity, operation2, context, option); - for (const after of wholeAfterFns) { - after(); - } - return result; +for (const before of wholeBeforeFns) { + before(); +} +const count = this.doUpdateSingleRow(entity, operation2, context, option); +for (const after of wholeAfterFns) { + after(); +} +return result; } /** @@ -1373,122 +1399,122 @@ export abstract class CascadeStore exten * @param option */ protected async cascadeUpdateAsync, OP extends OperateOption>( - entity: T, - operation: ED[T]['Operation'], - context: Cxt, - option: OP): Promise> { + entity: T, + operation: ED[T]['Operation'], + context: Cxt, + option: OP): Promise < OperationResult < ED >> { reinforceOperation(this.getSchema(), entity, operation); const { action, data, filter, id } = operation; let opData: any; - const wholeBeforeFns: Array<() => Promise> = []; - const wholeAfterFns: Array<() => Promise> = []; - const result: OperationResult = {}; + const wholeBeforeFns: Array<() => Promise> =[]; +const wholeAfterFns: Array<() => Promise> = []; +const result: OperationResult = {}; - if (['create', 'create-l'].includes(action) && data instanceof Array) { - opData = []; - for (const d of data) { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - d, - context, - option, - this.cascadeUpdateAsync, - ); - opData.push(od); - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); - } - } - else { - const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( - entity, - action, - data, - context, - option, - this.cascadeUpdateAsync, - filter - ); - opData = od; - wholeBeforeFns.push(...beforeFns); - wholeAfterFns.push(...afterFns); - } +if (['create', 'create-l'].includes(action) && data instanceof Array) { + opData = []; + for (const d of data) { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + d, + context, + option, + this.cascadeUpdateAsync, + ); + opData.push(od); + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); + } +} +else { + const { data: od, beforeFns, afterFns } = this.destructCascadeUpdate( + entity, + action, + data, + context, + option, + this.cascadeUpdateAsync, + filter + ); + opData = od; + wholeBeforeFns.push(...beforeFns); + wholeAfterFns.push(...afterFns); +} - const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = - Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { - data: opData as ED[T]['OpSchema'], - }); +const operation2: ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'] = + Object.assign({}, operation as ED[T]['CreateSingle'] | ED[T]['Update'] | ED[T]['Remove'], { + data: opData as ED[T]['OpSchema'], + }); - for (const before of wholeBeforeFns) { - await before(); - } - const count = await this.doUpdateSingleRowAsync(entity, operation2, context, option); - this.mergeOperationResult(result, { - [entity]: { - [operation2.action]: count, - } - } as OperationResult); - for (const after of wholeAfterFns) { - await after(); - } - return result; +for (const before of wholeBeforeFns) { + await before(); +} +const count = await this.doUpdateSingleRowAsync(entity, operation2, context, option); +this.mergeOperationResult(result, { + [entity]: { + [operation2.action]: count, + } +} as OperationResult); +for (const after of wholeAfterFns) { + await after(); +} +return result; } protected cascadeSelect>( - entity: T, - 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, - data, - context, - this.cascadeSelect, - this.aggregateSync, - option); + entity: T, + selection: ED[T]['Selection'], + context: Cxt, + option: OP): Partial < ED[T]['Schema'] > [] { + reinforceSelection(this.getSchema(), entity, selection); + const { data, filter, indexFrom, count, sorter } = selection; + const { projection, cascadeSelectionFns } = this.destructCascadeSelect( + entity, + data, + context, + this.cascadeSelect, + this.aggregateSync, + option); - const rows = this.selectAbjointRow(entity, { - data: projection, - filter, - indexFrom, - count, - sorter - }, context, option); + const rows = this.selectAbjointRow(entity, { + data: projection, + filter, + indexFrom, + count, + sorter + }, context, option); - if (cascadeSelectionFns.length > 0) { - const ruException: Array<{ - entity: keyof ED, - selection: ED[keyof ED]['Selection'] - }> = []; - cascadeSelectionFns.forEach( - ele => { - try { - ele(rows); + if (cascadeSelectionFns.length > 0) { + const ruException: Array<{ + entity: keyof ED, + selection: ED[keyof ED]['Selection'] + }> = []; + cascadeSelectionFns.forEach( + ele => { + try { + ele(rows); + } + catch (e) { + if (e instanceof OakRowUnexistedException) { + const rows = e.getRows(); + ruException.push(...rows); } - catch (e) { - if (e instanceof OakRowUnexistedException) { - const rows = e.getRows(); - ruException.push(...rows); - } - else { - throw e; - } + else { + throw e; } } - ) - - if (ruException.length > 0) { - throw new OakRowUnexistedException(ruException); } - } + ) - return rows; + if (ruException.length > 0) { + throw new OakRowUnexistedException(ruException); + } } + return rows; +} + /** * 将一次查询的结果集加入result * todo 如果是supportMtoOJoin,这里还要解构(未充分测试) @@ -1496,90 +1522,90 @@ export abstract class CascadeStore exten * @param rows * @param context */ - private addToResultSelections>(entity: T, rows: Partial[], context: Cxt) { - if (this.supportManyToOneJoin()) { - const attrsToPick: string[] = []; - for (const attr in rows[0]) { - const data: Partial = {} - const rel = this.judgeRelation(entity, attr); - if (rel === 2) { - this.addToResultSelections(attr, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); - } - else if (typeof rel === 'string') { - this.addToResultSelections(rel, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); - } - else if (rel instanceof Array) { - this.addToResultSelections(rel[0], rows.map(ele => ele[attr]!).reduce((prev, current) => prev.concat(current), [] as any[]), context); - } - else { - attrsToPick.push(attr); - } + private addToResultSelections>(entity: T, rows: Partial < ED[T]['Schema'] > [], context: Cxt) { + if (this.supportManyToOneJoin()) { + const attrsToPick: string[] = []; + for (const attr in rows[0]) { + const data: Partial = {} + const rel = this.judgeRelation(entity, attr); + if (rel === 2) { + this.addToResultSelections(attr, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); + } + else if (typeof rel === 'string') { + this.addToResultSelections(rel, rows.map(ele => ele[attr]!).filter(ele => !!ele), context); + } + else if (rel instanceof Array) { + this.addToResultSelections(rel[0], rows.map(ele => ele[attr]!).reduce((prev, current) => prev.concat(current), [] as any[]), context); + } + else { + attrsToPick.push(attr); } - const originRows = rows.map( - ele => pick(ele, attrsToPick) - ) as Partial[]; - this.addSingleRowToResultSelections(entity, originRows, context); - } - else { - this.addSingleRowToResultSelections(entity, rows, context); } + const originRows = rows.map( + ele => pick(ele, attrsToPick) + ) as Partial[]; + this.addSingleRowToResultSelections(entity, originRows, context); } + else { + this.addSingleRowToResultSelections(entity, rows, context); + } +} - private addSingleRowToResultSelections>(entity: T, rows: Partial[], context: Cxt) { - const { opRecords } = context; + private addSingleRowToResultSelections>(entity: T, rows: Partial < ED[T]['OpSchema'] > [], context: Cxt) { + const { opRecords } = context; - let lastOperation = opRecords[opRecords.length - 1]; - if (lastOperation && lastOperation.a === 's') { - const entityBranch = lastOperation.d[entity]; - if (entityBranch) { - rows.forEach( - (row) => { - if (row) { - assert(row.id); - const { id } = row; - if (!entityBranch![id!]) { - Object.assign(entityBranch!, { - [id!]: row, - }); - } - else { - Object.assign(entityBranch[id], row); - } + let lastOperation = opRecords[opRecords.length - 1]; + if (lastOperation && lastOperation.a === 's') { + const entityBranch = lastOperation.d[entity]; + if (entityBranch) { + rows.forEach( + (row) => { + if (row) { + assert(row.id); + const { id } = row; + if (!entityBranch![id!]) { + Object.assign(entityBranch!, { + [id!]: row, + }); + } + else { + Object.assign(entityBranch[id], row); } } - ); - return; - } - } - else { - lastOperation = { - a: 's', - d: {}, - }; - opRecords.push(lastOperation); - } - - const entityBranch = {}; - rows.forEach( - (row) => { - if (row) { - const { id } = row as { id: string }; - Object.assign(entityBranch!, { - [id!]: row, - }); } - } - ); - Object.assign(lastOperation.d, { - [entity]: entityBranch, - }); + ); + return; + } + } + else { + lastOperation = { + a: 's', + d: {}, + }; + opRecords.push(lastOperation); } + const entityBranch = {}; + rows.forEach( + (row) => { + if (row) { + const { id } = row as { id: string }; + Object.assign(entityBranch!, { + [id!]: row, + }); + } + } + ); + Object.assign(lastOperation.d, { + [entity]: entityBranch, + }); +} + protected async cascadeSelectAsync>( - entity: T, - selection: ED[T]['Selection'], - context: Cxt, - option: OP): Promise[]> { + entity: T, + selection: ED[T]['Selection'], + context: Cxt, + option: OP): Promise < Partial < ED[T]['Schema'] > [] > { reinforceSelection(this.getSchema(), entity, selection); const { data, filter, indexFrom, count, sorter } = selection; const { projection, cascadeSelectionFns } = this.destructCascadeSelect( @@ -1599,39 +1625,39 @@ export abstract class CascadeStore exten }, context, option); - if (!option.dontCollect) { - this.addToResultSelections(entity, rows, context); - } + if(!option.dontCollect) { + this.addToResultSelections(entity, rows, context); +} - if (cascadeSelectionFns.length > 0) { - const ruException: Array<{ - entity: keyof ED, - selection: ED[keyof ED]['Selection'] - }> = []; - await Promise.all( - cascadeSelectionFns.map( - async ele => { - try { - await ele(rows); - } - catch (e) { - if (e instanceof OakRowUnexistedException) { - const rows = e.getRows(); - ruException.push(...rows); - } - else { - throw e; - } - } +if (cascadeSelectionFns.length > 0) { + const ruException: Array<{ + entity: keyof ED, + selection: ED[keyof ED]['Selection'] + }> = []; + await Promise.all( + cascadeSelectionFns.map( + async ele => { + try { + await ele(rows); + } + catch (e) { + if (e instanceof OakRowUnexistedException) { + const rows = e.getRows(); + ruException.push(...rows); } - ) - ); - - if (ruException.length > 0) { - throw new OakRowUnexistedException(ruException); + else { + throw e; + } + } } - } + ) + ); - return rows; + if (ruException.length > 0) { + throw new OakRowUnexistedException(ruException); + } +} + +return rows; } } \ No newline at end of file diff --git a/src/store/modi.ts b/src/store/modi.ts index 14211ef..2fe1233 100644 --- a/src/store/modi.ts +++ b/src/store/modi.ts @@ -151,7 +151,8 @@ export function createModiRelatedTriggers { - const { data } = operation; - const { id } = data; + const { filter } = operation; await context.operate('modiEntity', { id: await generateNewIdAsync(), action: 'remove', data: {}, filter: { modi: { - entity, - entityId: id, + [entity]: filter, + iState: 'active', }, } }, { dontCollect: true }); @@ -177,8 +177,8 @@ export function createModiRelatedTriggers(schema: StorageSchema< const toBeAssignNode2: Record = {}; // 用来记录在表达式中涉及到的结点 const projectionNodeDict: Record = {}; const checkProjectionNode = (entity2: keyof ED, projectionNode: ED[keyof ED]['Selection']['data']) => { - const necessaryAttrs: string[] = ['id', '$$createAt$$']; // 因有的页面依赖于其他页面的数据,因此默认的createAt的filter不一定会加上,为了保险起见在这里统一加上 + const necessaryAttrs: string[] = ['id', '$$createAt$$']; // 有的页面依赖于其它页面取数据,有时两个页面的filter的差异会导致有一个加createAt,有一个不加,此时可能产生前台取数据不完整的异常。先统一加上 for (const attr in projectionNode) { if (attr === '#id') { assert(!projectionNodeDict[projectionNode[attr]!], `projection中结点的id有重复, ${projectionNode[attr]}`); diff --git a/src/types/Aspect.ts b/src/types/Aspect.ts index f083d8e..bdd5848 100644 --- a/src/types/Aspect.ts +++ b/src/types/Aspect.ts @@ -9,6 +9,7 @@ export interface Aspect>{ export interface AspectWrapper, AD extends Record>>{ exec: (name: T, params: Parameters[0]) => Promise<{ result: Awaited>; - opRecords: OpRecord[]; + opRecords?: OpRecord[]; + message?: string | null; }>; }; \ No newline at end of file diff --git a/src/types/Auth.ts b/src/types/Auth.ts index cee1553..048f049 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 | '@entity')]?: ActionOnRemove; + [E in (keyof ED | keyof ED[T]['Schema'] | '@entity')]?: ActionOnRemove; } }; diff --git a/src/types/Connector.ts b/src/types/Connector.ts index 9876113..737f57e 100644 --- a/src/types/Connector.ts +++ b/src/types/Connector.ts @@ -8,7 +8,8 @@ import { OakException } from "./Exception"; export abstract class Connector, FrontCxt extends SyncContext> { abstract callAspect(name: string, params: any, context: FrontCxt): Promise<{ result: any; - opRecords: OpRecord[]; + opRecords?: OpRecord[]; + message?: string | null; }>; abstract getRouter(): string; diff --git a/src/utils/SimpleConnector.ts b/src/utils/SimpleConnector.ts index ea20eeb..7c7f714 100644 --- a/src/utils/SimpleConnector.ts +++ b/src/utils/SimpleConnector.ts @@ -35,7 +35,7 @@ export class SimpleConnector[]; }> { + async callAspect(name: string, params: any, context: FrontCxt) { const cxtStr = context.toString(); const { contentType, body } = makeContentTypeAndBody(params); @@ -53,20 +53,34 @@ export class SimpleConnector) { + const urlSp = new URLSearchParams(params); + if (url.includes('?')) { + return `${url}&${urlSp}`; + } + return `${url}?${urlSp}`; +} \ No newline at end of file