This commit is contained in:
Xu Chang 2023-02-15 16:09:52 +08:00
commit 6e5d7d5bfe
22 changed files with 916 additions and 781 deletions

View File

@ -7,6 +7,7 @@ export declare abstract class AsyncContext<ED extends EntityDict> implements Con
opRecords: OpRecord<ED>[]; opRecords: OpRecord<ED>[];
private scene?; private scene?;
private headers?; private headers?;
private message?;
events: { events: {
commit: Array<() => Promise<void>>; commit: Array<() => Promise<void>>;
rollback: Array<() => Promise<void>>; rollback: Array<() => Promise<void>>;
@ -32,6 +33,8 @@ export declare abstract class AsyncContext<ED extends EntityDict> implements Con
mergeMultipleResults(toBeMerged: OperationResult<ED>[]): OperationResult<ED>; mergeMultipleResults(toBeMerged: OperationResult<ED>[]): OperationResult<ED>;
getCurrentTxnId(): string | undefined; getCurrentTxnId(): string | undefined;
getSchema(): import("../types").StorageSchema<ED>; getSchema(): import("../types").StorageSchema<ED>;
setMessage(message: string): void;
getMessage(): string | undefined;
abstract isRoot(): boolean; abstract isRoot(): boolean;
abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined; abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined;
abstract toString(): string; abstract toString(): string;

View File

@ -176,6 +176,12 @@ var AsyncContext = /** @class */ (function () {
AsyncContext.prototype.getSchema = function () { AsyncContext.prototype.getSchema = function () {
return this.rowStore.getSchema(); return this.rowStore.getSchema();
}; };
AsyncContext.prototype.setMessage = function (message) {
this.message = message;
};
AsyncContext.prototype.getMessage = function () {
return this.message;
};
return AsyncContext; return AsyncContext;
}()); }());
exports.AsyncContext = AsyncContext; exports.AsyncContext = AsyncContext;

View File

@ -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 _g = tslib_1.__read(relation, 2), entityOtm_1 = _g[0], foreignKey_2 = _g[1];
var otmOperations = data[attr]; var otmOperations = data[attr];
var dealWithOneToMany = function (otm) { 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; var actionOtm = otm.action, dataOtm = otm.data, filterOtm = otm.filter;
if (!foreignKey_2) { if (!foreignKey_2) {
// 基于entity/entityId的one-to-many // 基于entity/entityId的one-to-many
@ -581,13 +581,25 @@ var CascadeStore = /** @class */ (function (_super) {
} }
} }
else { else {
// 这里先假设A必是update的filter上一定有id否则用户界面上应该设计不出来这样的操作 // 这里优化一下如果filter上有id直接更新成根据entityId来过滤
// 这个倒是好像不可能出现create/update的一对多如果遇到了再完善 // 除了性能原因之外还因为会制造出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, { Object.assign(otm, {
filter: (0, filter_1.addFilterSegment)((_a = {}, filter: (0, filter_1.addFilterSegment)((_a = {},
_a[entity] = filter, _a[entity] = filter,
_a), filterOtm), _a), filterOtm),
}); });
}
}
if (action === 'remove' && actionOtm === 'update') { if (action === 'remove' && actionOtm === 'update') {
Object.assign(dataOtm, { Object.assign(dataOtm, {
entity: null, entity: null,
@ -634,20 +646,34 @@ var CascadeStore = /** @class */ (function (_super) {
} }
} }
else { else {
// update可能出现上层filter不是根据id的userEntityGrant的过期触发的wechatQrCode的过期见general中的userEntityGrant的trigger // 这里优化一下如果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, { Object.assign(otm, {
filter: (0, filter_1.addFilterSegment)((_d = {}, filter: (0, filter_1.addFilterSegment)((_d = {},
_d[foreignKey_2.slice(0, foreignKey_2.length - 2)] = filter, _d[foreignKey_2] = filter.id,
_d), filterOtm), _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') { if (action === 'remove' && actionOtm === 'update') {
Object.assign(dataOtm, (_e = {}, Object.assign(dataOtm, (_f = {},
_e[foreignKey_2] = null, _f[foreignKey_2] = null,
_e)); _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) { if (otmOperations instanceof Array) {
try { try {

View File

@ -153,7 +153,8 @@ function createModiRelatedTriggers(schema) {
var _loop_2 = function (entity) { var _loop_2 = function (entity) {
var inModi = schema[entity].inModi; var inModi = schema[entity].inModi;
if (inModi) { if (inModi) {
// 当关联modi的对象被删除时对应的modi也删除 // 当关联modi的对象被删除时对应的modi也删除。这里似乎只需要删除掉活跃对象因为oper不能删除所以oper和modi是必须要支持对deleted对象的容错
// 这里没有想清楚by Xc 20230209
triggers.push({ triggers.push({
name: "\u5F53\u5220\u9664".concat(entity, "\u5BF9\u8C61\u65F6\uFF0C\u5220\u9664\u76F8\u5173\u8054\u7684modi\u7684modiEntity"), name: "\u5F53\u5220\u9664".concat(entity, "\u5BF9\u8C61\u65F6\uFF0C\u5220\u9664\u76F8\u5173\u8054\u7684modi\u7684modiEntity"),
action: 'remove', action: 'remove',
@ -163,43 +164,42 @@ function createModiRelatedTriggers(schema) {
fn: function (_a, context, option) { fn: function (_a, context, option) {
var operation = _a.operation; var operation = _a.operation;
return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
var data, id, _b, _c, _d, _e, _f, _g; var filter, _b, _c, _d, _e, _f, _g;
var _h, _j; var _h, _j, _k, _l;
return tslib_1.__generator(this, function (_k) { return tslib_1.__generator(this, function (_m) {
switch (_k.label) { switch (_m.label) {
case 0: case 0:
data = operation.data; filter = operation.filter;
id = data.id;
_c = (_b = context).operate; _c = (_b = context).operate;
_d = ['modiEntity']; _d = ['modiEntity'];
_h = {}; _h = {};
return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; 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.action = 'remove',
_h.data = {}, _h.data = {},
_h.filter = { _h.filter = {
modi: { modi: (_j = {},
entity: entity, _j[entity] = filter,
entityId: id, _j.iState = 'active',
}, _j),
}, },
_h), { dontCollect: true }]))]; _h), { dontCollect: true }]))];
case 2: case 2:
_k.sent(); _m.sent();
_f = (_e = context).operate; _f = (_e = context).operate;
_g = ['modi']; _g = ['modi'];
_j = {}; _k = {};
return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()];
case 3: return [4 /*yield*/, _f.apply(_e, _g.concat([(_j.id = _k.sent(), case 3: return [4 /*yield*/, _f.apply(_e, _g.concat([(_k.id = _m.sent(),
_j.action = 'remove', _k.action = 'remove',
_j.data = {}, _k.data = {},
_j.filter = { _k.filter = (_l = {},
entity: entity, _l[entity] = filter,
entityId: id, _l.iState = 'active',
}, _l),
_j), { dontCollect: true }]))]; _k), { dontCollect: true }]))];
case 4: case 4:
_k.sent(); _m.sent();
return [2 /*return*/, 0]; return [2 /*return*/, 0];
} }
}); });

View File

@ -150,7 +150,7 @@ function reinforceSelection(schema, entity, selection) {
var projectionNodeDict = {}; var projectionNodeDict = {};
var checkProjectionNode = function (entity2, projectionNode) { var checkProjectionNode = function (entity2, projectionNode) {
var _a, _b, _c; var _a, _b, _c;
var necessaryAttrs = ['id', '$$createAt$$']; // 因有的页面依赖于其他页面的数据因此默认的createAt的filter不一定会加上为了保险起见在这里统一加上 var necessaryAttrs = ['id', '$$createAt$$']; // 有的页面依赖于其它页面取数据有时两个页面的filter的差异会导致有一个加createAt有一个不加此时可能产生前台取数据不完整的异常。先统一加上
for (var attr in projectionNode) { for (var attr in projectionNode) {
if (attr === '#id') { if (attr === '#id') {
(0, assert_1.default)(!projectionNodeDict[projectionNode[attr]], "projection\u4E2D\u7ED3\u70B9\u7684id\u6709\u91CD\u590D, ".concat(projectionNode[attr])); (0, assert_1.default)(!projectionNodeDict[projectionNode[attr]], "projection\u4E2D\u7ED3\u70B9\u7684id\u6709\u91CD\u590D, ".concat(projectionNode[attr]));

View File

@ -7,6 +7,7 @@ export interface Aspect<ED extends EntityDict, Cxt extends AsyncContext<ED>> {
export interface AspectWrapper<ED extends EntityDict, Cxt extends AsyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>> { export interface AspectWrapper<ED extends EntityDict, Cxt extends AsyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>> {
exec: <T extends keyof AD>(name: T, params: Parameters<AD[T]>[0]) => Promise<{ exec: <T extends keyof AD>(name: T, params: Parameters<AD[T]>[0]) => Promise<{
result: Awaited<ReturnType<AD[T]>>; result: Awaited<ReturnType<AD[T]>>;
opRecords: OpRecord<ED>[]; opRecords?: OpRecord<ED>[];
message?: string | null;
}>; }>;
} }

2
lib/types/Auth.d.ts vendored
View File

@ -61,7 +61,7 @@ export declare type AuthDef<ED extends EntityDict, T extends keyof ED> = {
relationAuth?: CascadeRelationAuth<NonNullable<ED[T]['Relation']>>; relationAuth?: CascadeRelationAuth<NonNullable<ED[T]['Relation']>>;
actionAuth?: CascadeActionAuth<ED[T]['Action']>; actionAuth?: CascadeActionAuth<ED[T]['Action']>;
cascadeRemove?: { cascadeRemove?: {
[E in (keyof ED | '@entity')]?: ActionOnRemove; [E in (keyof ED | keyof ED[T]['Schema'] | '@entity')]?: ActionOnRemove;
}; };
}; };
export declare type AuthDefDict<ED extends EntityDict> = { export declare type AuthDefDict<ED extends EntityDict> = {

View File

@ -7,7 +7,8 @@ import { OakException } from "./Exception";
export declare abstract class Connector<ED extends EntityDict, BackCxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> { export declare abstract class Connector<ED extends EntityDict, BackCxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> {
abstract callAspect(name: string, params: any, context: FrontCxt): Promise<{ abstract callAspect(name: string, params: any, context: FrontCxt): Promise<{
result: any; result: any;
opRecords: OpRecord<ED>[]; opRecords?: OpRecord<ED>[];
message?: string | null;
}>; }>;
abstract getRouter(): string; abstract getRouter(): string;
abstract parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore<ED, BackCxt>): Promise<{ abstract parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore<ED, BackCxt>): Promise<{

View File

@ -2,7 +2,7 @@
import { IncomingHttpHeaders } from "http"; import { IncomingHttpHeaders } from "http";
import { AsyncContext, AsyncRowStore } from '../store/AsyncRowStore'; import { AsyncContext, AsyncRowStore } from '../store/AsyncRowStore';
import { SyncContext } from '../store/SyncRowStore'; import { SyncContext } from '../store/SyncRowStore';
import { Connector, EntityDict, OakException, OpRecord } from "../types"; import { Connector, EntityDict, OakException } from "../types";
export declare class SimpleConnector<ED extends EntityDict, BackCxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> extends Connector<ED, BackCxt, FrontCxt> { export declare class SimpleConnector<ED extends EntityDict, BackCxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> extends Connector<ED, BackCxt, FrontCxt> {
static ROUTER: string; static ROUTER: string;
private serverUrl; private serverUrl;
@ -11,7 +11,12 @@ export declare class SimpleConnector<ED extends EntityDict, BackCxt extends Asyn
constructor(serverUrl: string, makeException: (exceptionData: any) => OakException<ED>, contextBuilder: (str: string | undefined) => (store: AsyncRowStore<ED, BackCxt>) => Promise<BackCxt>); constructor(serverUrl: string, makeException: (exceptionData: any) => OakException<ED>, contextBuilder: (str: string | undefined) => (store: AsyncRowStore<ED, BackCxt>) => Promise<BackCxt>);
callAspect(name: string, params: any, context: FrontCxt): Promise<{ callAspect(name: string, params: any, context: FrontCxt): Promise<{
result: any; result: any;
opRecords: OpRecord<ED>[]; opRecords: any;
message: string | null;
} | {
result: ArrayBuffer;
message: string | null;
opRecords?: undefined;
}>; }>;
getRouter(): string; getRouter(): string;
parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore<ED, BackCxt>): Promise<{ parseRequest(headers: IncomingHttpHeaders, body: any, store: AsyncRowStore<ED, BackCxt>): Promise<{

View File

@ -31,7 +31,7 @@ var SimpleConnector = /** @class */ (function (_super) {
} }
SimpleConnector.prototype.callAspect = function (name, params, context) { SimpleConnector.prototype.callAspect = function (name, params, context) {
return tslib_1.__awaiter(this, void 0, void 0, function () { 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) { return tslib_1.__generator(this, function (_c) {
switch (_c.label) { switch (_c.label) {
case 0: 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)); err = new types_1.OakExternalException("\u7F51\u7EDC\u8BF7\u6C42\u8FD4\u56DE\u5F02\u5E38\uFF0Cstatus\u662F".concat(response.status));
throw err; 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()]; return [4 /*yield*/, response.json()];
case 2: case 2:
_b = _c.sent(), exception = _b.exception, result = _b.result, opRecords = _b.opRecords; _b = _c.sent(), exception = _b.exception, result = _b.result, opRecords = _b.opRecords;
@ -61,7 +64,18 @@ var SimpleConnector = /** @class */ (function (_super) {
return [2 /*return*/, { return [2 /*return*/, {
result: result, result: result,
opRecords: opRecords, 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, result: result,
opRecords: context.opRecords, opRecords: context.opRecords,
}, },
headers: {
'oak-message': context.getMessage(),
},
}; };
}; };
SimpleConnector.prototype.serializeException = function (exception, headers, body) { SimpleConnector.prototype.serializeException = function (exception, headers, body) {

1
lib/utils/url.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function composeUrl(url: string, params: Record<string, any>): string;

12
lib/utils/url.js Normal file
View File

@ -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;

View File

@ -1,6 +1,6 @@
{ {
"name": "oak-domain", "name": "oak-domain",
"version": "2.6.1", "version": "2.6.2",
"author": { "author": {
"name": "XuChang" "name": "XuChang"
}, },

View File

@ -9,6 +9,7 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
opRecords: OpRecord<ED>[]; opRecords: OpRecord<ED>[];
private scene?: string; private scene?: string;
private headers?: IncomingHttpHeaders; private headers?: IncomingHttpHeaders;
private message?: string;
events: { events: {
commit: Array<() => Promise<void>>; commit: Array<() => Promise<void>>;
rollback: Array<() => Promise<void>>; rollback: Array<() => Promise<void>>;
@ -128,6 +129,14 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
return this.rowStore.getSchema(); return this.rowStore.getSchema();
} }
setMessage(message: string) {
this.message = message;
}
getMessage() {
return this.message;
}
abstract isRoot(): boolean; abstract isRoot(): boolean;
abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined; abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined;

View File

@ -726,13 +726,25 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
} }
} }
else { else {
// 这里先假设A必是update的filter上一定有id否则用户界面上应该设计不出来这样的操作 // 这里优化一下如果filter上有id直接更新成根据entityId来过滤
// 这个倒是好像不可能出现create/update的一对多如果遇到了再完善 // 除了性能原因之外还因为会制造出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, { Object.assign(otm, {
filter: addFilterSegment({ filter: addFilterSegment({
[entity]: filter, [entity]: filter,
}, filterOtm), }, filterOtm),
}); });
}
}
if (action === 'remove' && actionOtm === 'update') { if (action === 'remove' && actionOtm === 'update') {
Object.assign(dataOtm, { Object.assign(dataOtm, {
entity: null, entity: null,
@ -777,12 +789,25 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
} }
} }
else { else {
// update可能出现上层filter不是根据id的userEntityGrant的过期触发的wechatQrCode的过期见general中的userEntityGrant的trigger // 这里优化一下如果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, { Object.assign(otm, {
filter: addFilterSegment({ filter: addFilterSegment({
[foreignKey.slice(0, foreignKey.length - 2)]: filter, [foreignKey.slice(0, foreignKey.length - 2)]: filter,
}, filterOtm), }, filterOtm),
}); });
}
}
if (action === 'remove' && actionOtm === 'update') { if (action === 'remove' && actionOtm === 'update') {
Object.assign(dataOtm, { Object.assign(dataOtm, {
[foreignKey]: null, [foreignKey]: null,
@ -791,7 +816,8 @@ export abstract class CascadeStore<ED extends EntityDict & BaseEntityDict> exten
} }
} }
beforeFns.push(() => cascadeUpdate.call(this, entityOtm!, otm, context, option2)); // 一对多的依赖应该后建否则中间会出现空指针导致checker等出错
afterFns.push(() => cascadeUpdate.call(this, entityOtm!, otm, context, option2));
}; };
if (otmOperations instanceof Array) { if (otmOperations instanceof Array) {

View File

@ -151,7 +151,8 @@ export function createModiRelatedTriggers<ED extends EntityDict & BaseEntityDict
for (const entity in schema) { for (const entity in schema) {
const { inModi } = schema[entity]; const { inModi } = schema[entity];
if (inModi) { if (inModi) {
// 当关联modi的对象被删除时对应的modi也删除 // 当关联modi的对象被删除时对应的modi也删除。这里似乎只需要删除掉活跃对象因为oper不能删除所以oper和modi是必须要支持对deleted对象的容错
// 这里没有想清楚by Xc 20230209
triggers.push({ triggers.push({
name: `当删除${entity}对象时删除相关联的modi的modiEntity`, name: `当删除${entity}对象时删除相关联的modi的modiEntity`,
action: 'remove', action: 'remove',
@ -159,16 +160,15 @@ export function createModiRelatedTriggers<ED extends EntityDict & BaseEntityDict
when: 'after', when: 'after',
priority: REMOVE_CASCADE_PRIORITY, priority: REMOVE_CASCADE_PRIORITY,
fn: async ({ operation }, context, option) => { fn: async ({ operation }, context, option) => {
const { data } = operation; const { filter } = operation;
const { id } = data;
await context.operate('modiEntity', { await context.operate('modiEntity', {
id: await generateNewIdAsync(), id: await generateNewIdAsync(),
action: 'remove', action: 'remove',
data: {}, data: {},
filter: { filter: {
modi: { modi: {
entity, [entity]: filter,
entityId: id, iState: 'active',
}, },
} }
}, { dontCollect: true }); }, { dontCollect: true });
@ -177,8 +177,8 @@ export function createModiRelatedTriggers<ED extends EntityDict & BaseEntityDict
action: 'remove', action: 'remove',
data: {}, data: {},
filter: { filter: {
entity, [entity]: filter,
entityId: id, iState: 'active',
} }
}, { dontCollect: true }); }, { dontCollect: true });
return 0; return 0;

View File

@ -154,7 +154,7 @@ export function reinforceSelection<ED extends EntityDict>(schema: StorageSchema<
const toBeAssignNode2: Record<string, string[]> = {}; // 用来记录在表达式中涉及到的结点 const toBeAssignNode2: Record<string, string[]> = {}; // 用来记录在表达式中涉及到的结点
const projectionNodeDict: Record<string, ED[keyof ED]['Selection']['data']> = {}; const projectionNodeDict: Record<string, ED[keyof ED]['Selection']['data']> = {};
const checkProjectionNode = (entity2: keyof ED, projectionNode: ED[keyof ED]['Selection']['data']) => { 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) { for (const attr in projectionNode) {
if (attr === '#id') { if (attr === '#id') {
assert(!projectionNodeDict[projectionNode[attr]!], `projection中结点的id有重复, ${projectionNode[attr]}`); assert(!projectionNodeDict[projectionNode[attr]!], `projection中结点的id有重复, ${projectionNode[attr]}`);

View File

@ -9,6 +9,7 @@ export interface Aspect<ED extends EntityDict, Cxt extends AsyncContext<ED>>{
export interface AspectWrapper<ED extends EntityDict, Cxt extends AsyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>>{ export interface AspectWrapper<ED extends EntityDict, Cxt extends AsyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>>{
exec: <T extends keyof AD>(name: T, params: Parameters<AD[T]>[0]) => Promise<{ exec: <T extends keyof AD>(name: T, params: Parameters<AD[T]>[0]) => Promise<{
result: Awaited<ReturnType<AD[T]>>; result: Awaited<ReturnType<AD[T]>>;
opRecords: OpRecord<ED>[]; opRecords?: OpRecord<ED>[];
message?: string | null;
}>; }>;
}; };

View File

@ -89,7 +89,7 @@ export type AuthDef<ED extends EntityDict, T extends keyof ED> = {
relationAuth?: CascadeRelationAuth<NonNullable<ED[T]['Relation']>>; relationAuth?: CascadeRelationAuth<NonNullable<ED[T]['Relation']>>;
actionAuth?: CascadeActionAuth<ED[T]['Action']>; actionAuth?: CascadeActionAuth<ED[T]['Action']>;
cascadeRemove?: { cascadeRemove?: {
[E in (keyof ED | '@entity')]?: ActionOnRemove; [E in (keyof ED | keyof ED[T]['Schema'] | '@entity')]?: ActionOnRemove;
} }
}; };

View File

@ -8,7 +8,8 @@ import { OakException } from "./Exception";
export abstract class Connector<ED extends EntityDict, BackCxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> { export abstract class Connector<ED extends EntityDict, BackCxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>> {
abstract callAspect(name: string, params: any, context: FrontCxt): Promise<{ abstract callAspect(name: string, params: any, context: FrontCxt): Promise<{
result: any; result: any;
opRecords: OpRecord<ED>[]; opRecords?: OpRecord<ED>[];
message?: string | null;
}>; }>;
abstract getRouter(): string; abstract getRouter(): string;

View File

@ -35,7 +35,7 @@ export class SimpleConnector<ED extends EntityDict, BackCxt extends AsyncContext
this.contextBuilder = contextBuilder; this.contextBuilder = contextBuilder;
} }
async callAspect(name: string, params: any, context: FrontCxt): Promise<{ result: any; opRecords: OpRecord<ED>[]; }> { async callAspect(name: string, params: any, context: FrontCxt) {
const cxtStr = context.toString(); const cxtStr = context.toString();
const { contentType, body } = makeContentTypeAndBody(params); const { contentType, body } = makeContentTypeAndBody(params);
@ -53,11 +53,13 @@ export class SimpleConnector<ED extends EntityDict, BackCxt extends AsyncContext
throw err; throw err;
} }
// todo 处理各种返回的格式 const message = response.headers.get('oak-message');
const responseType = response.headers.get('Content-Type');
if (responseType?.toLocaleLowerCase().match(/application\/json/i)) {
const { const {
exception, exception,
result, result,
opRecords opRecords,
} = await response.json(); } = await response.json();
if (exception) { if (exception) {
@ -66,8 +68,20 @@ export class SimpleConnector<ED extends EntityDict, BackCxt extends AsyncContext
return { return {
result, result,
opRecords, opRecords,
message,
}; };
} }
else if (responseType?.toLocaleLowerCase().match(/application\/octet-stream/i)) {
const result = await response.arrayBuffer();
return {
result,
message,
};
}
else {
throw new Error(`尚不支持的content-type类型${responseType}`);
}
}
getRouter(): string { getRouter(): string {
return SimpleConnector.ROUTER; return SimpleConnector.ROUTER;
@ -97,6 +111,9 @@ export class SimpleConnector<ED extends EntityDict, BackCxt extends AsyncContext
result, result,
opRecords: context.opRecords, opRecords: context.opRecords,
}, },
headers: {
'oak-message': context.getMessage(),
},
}; };
} }

9
src/utils/url.ts Normal file
View File

@ -0,0 +1,9 @@
import { URLSearchParams } from 'url';
export function composeUrl(url: string, params: Record<string, any>) {
const urlSp = new URLSearchParams(params);
if (url.includes('?')) {
return `${url}&${urlSp}`;
}
return `${url}?${urlSp}`;
}