Merge branch 'dev'

This commit is contained in:
Xu Chang 2024-03-29 18:31:40 +08:00
commit a31f5a2015
16 changed files with 68 additions and 56 deletions

View File

@ -50,7 +50,7 @@ export declare abstract class AsyncContext<ED extends EntityDict> implements Con
abstract getCurrentUserId(allowUnloggedIn?: boolean): string | undefined;
abstract setCurrentUserId(userId: string | undefined): void;
abstract toString(): Promise<string>;
abstract initialize(data: any, later?: boolean): Promise<void>;
abstract initialize(data?: any, later?: boolean): Promise<void>;
abstract allowUserUpdate(): boolean;
abstract openRootMode(): () => void;
}

View File

@ -77,7 +77,7 @@ function createUniqueCheckers(schema) {
entity,
action: 'create',
type: 'logicalData',
priority: types_1.CHECKER_MAX_PRIORITY, // 优先级要放在最低所有前置的checker/trigger将数据完整之后再在这里检测
priority: types_1.CHECKER_MAX_PRIORITY,
checker: (operation, context) => {
const { data } = operation;
if (data instanceof Array) {
@ -93,9 +93,9 @@ function createUniqueCheckers(schema) {
}
}, {
entity,
action: 'update', // 只检查update其它状态转换的action应该不会涉及unique约束的属性
action: 'update',
type: 'logicalData',
priority: types_1.CHECKER_MAX_PRIORITY, // 优先级要放在最低所有前置的checker/trigger将数据完整之后再在这里检测
priority: types_1.CHECKER_MAX_PRIORITY,
checker: (operation, context) => {
const { data, filter: operationFilter } = operation;
if (data) {
@ -213,7 +213,7 @@ function createActionTransformerCheckers(actionDefDict) {
action: 'create',
type: 'logicalData',
entity,
priority: 10, // 优先级要高先于真正的data检查进行
priority: 10,
checker: (operation) => {
const { data } = operation;
if (data instanceof Array) {

View File

@ -441,10 +441,10 @@ class RelationAuth {
* @returns
*/
const makeCreateFilter = (entity, operation) => {
const { data, filter } = operation;
const { data, filter, bornAt } = operation;
(0, assert_1.default)(!(data instanceof Array));
if (data) {
return (0, filter_1.translateCreateDataToFilter)(this.schema, entity, data);
return (0, filter_1.translateCreateDataToFilter)(this.schema, entity, data, !!bornAt);
}
return filter;
};
@ -498,7 +498,7 @@ class RelationAuth {
}
(0, assert_1.default)(!(data instanceof Array));
for (const attr in data) {
const rel = (0, relation_1.judgeRelation)(this.schema, entity, attr);
const rel = (0, relation_1.judgeRelation)(this.schema, entity, attr, !!operation.bornAt);
if (rel === 2 && !isModiUpdate) {
(0, assert_1.default)(root === me && !hasParent, 'cascadeUpdate必须是树结构避免森林');
const mtoOperation = data[attr];

View File

@ -16,7 +16,7 @@ export declare class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt
private logger;
private contextBuilder;
private onVolatileTrigger;
constructor(contextBuilder: (cxtString?: string) => Promise<Cxt>, logger?: Logger, onVolatileTrigger?: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>);
constructor(contextBuilder: () => Cxt, logger?: Logger, onVolatileTrigger?: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>);
setOnVolatileTrigger(onVolatileTrigger: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>): void;
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>, schema: StorageSchema<ED>): void;
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>): void;

View File

@ -39,15 +39,21 @@ class TriggerExecutor {
this.volatileEntities = [];
this.counter = 0;
this.onVolatileTrigger = onVolatileTrigger || (async (entity, trigger, ids, cxtStr, option) => {
const context = await this.contextBuilder(cxtStr);
await context.begin();
const context = this.contextBuilder();
if (!context.getCurrentTxnId()) {
await context.begin();
}
await context.initialize(JSON.parse(cxtStr));
try {
await this.execVolatileTrigger(entity, trigger.name, ids, context, option);
await context.commit();
}
catch (err) {
await context.rollback();
this.logger.error('error on volatile trigger', entity, trigger.name, ids.join(','), err);
if (!(err instanceof types_1.OakMakeSureByMySelfException)) {
this.logger.error('error on volatile trigger', entity, trigger.name, ids.join(','), err);
}
// throw err;
}
});
}
@ -271,8 +277,8 @@ class TriggerExecutor {
}
}
const number = trigger.fn({ operation: operation }, context, option);
if (number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
}
(0, assert_1.default)(commitTriggers.length === 0, `前台不应有commitTrigger`);
@ -294,8 +300,8 @@ class TriggerExecutor {
}
}
const number = await trigger.fn({ operation: operation }, context, option);
if (number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
return execPreTrigger(idx + 1);
};
@ -382,8 +388,8 @@ class TriggerExecutor {
operation: operation,
result: result,
}, context, option);
if (number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
}
}
@ -398,8 +404,8 @@ class TriggerExecutor {
operation: operation,
result: result,
}, context, option);
if (number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
return execPostTrigger(idx + 1);
};
@ -441,7 +447,7 @@ class TriggerExecutor {
$lt: timestamp,
}
};
const context = await this.contextBuilder();
const context = this.contextBuilder();
if (context.clusterInfo?.usingCluster) {
const { instanceCount, instanceId } = context.clusterInfo;
filter.$$seq$$ = {

View File

@ -46,7 +46,7 @@ function translateCheckerInAsyncContext(checker, schema) {
case 'row': {
const { filter, errMsg, inconsistentRows } = checker;
const fn = (async ({ operation }, context, option) => {
const { filter: operationFilter, data, action } = operation;
const { filter: operationFilter, data, action, bornAt } = operation;
const filter2 = typeof filter === 'function' ? await filter(operation, context, option) : filter;
if (['select', 'count', 'stat'].includes(action)) {
operation.filter = (0, filter_1.combineFilters)(entity, context.getSchema(), [operationFilter, filter2]);
@ -88,11 +88,11 @@ function translateCheckerInAsyncContext(checker, schema) {
(0, assert_1.default)(data);
if (data instanceof Array) {
for (const d of data) {
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, d));
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, d, !!bornAt));
}
}
else {
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, data));
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt));
}
return;
}
@ -139,7 +139,7 @@ function translateCheckerInSyncContext(checker, schema) {
case 'row': {
const { filter, errMsg, entity } = checker;
const fn = (operation, context, option) => {
const { filter: operationFilter, data, action } = operation;
const { filter: operationFilter, data, action, bornAt } = operation;
const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter;
let operationFilter2 = operationFilter;
if (action === 'create') {
@ -147,7 +147,7 @@ function translateCheckerInSyncContext(checker, schema) {
// 前端的策略是有data用data无data用filter
// 目前前端应该不可能制造出来createMultiple
(0, assert_1.default)(!(data instanceof Array));
operationFilter2 = (0, filter_1.translateCreateDataToFilter)(schema, entity, data);
operationFilter2 = (0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt);
}
}
(0, assert_1.default)(!(filter2 instanceof Promise));

View File

@ -2,7 +2,7 @@ import { EntityDict as BaseEntityDict, StorageSchema } from '../types';
import { EntityDict } from "../base-app-domain";
import { AsyncContext } from './AsyncRowStore';
import { SyncContext } from './SyncRowStore';
export declare function translateCreateDataToFilter<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(schema: StorageSchema<ED>, entity: T, data: ED[T]['CreateSingle']['data']): ED[T]["Selection"]["filter"];
export declare function translateCreateDataToFilter<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(schema: StorageSchema<ED>, entity: T, data: ED[T]['CreateSingle']['data'], allowUnrecoganized: boolean): ED[T]["Selection"]["filter"];
export declare function combineFilters<ED extends EntityDict & BaseEntityDict, T extends keyof ED>(entity: T, schema: StorageSchema<ED>, filters: Array<ED[T]['Selection']['filter']>, union?: true): ED[T]["Selection"]["filter"] | undefined;
/**
* //filter在逻辑上相容或者相斥

View File

@ -6,10 +6,10 @@ const assert_1 = tslib_1.__importDefault(require("assert"));
const types_1 = require("../types");
const lodash_1 = require("../utils/lodash");
const relation_1 = require("./relation");
function translateCreateDataToFilter(schema, entity, data) {
function translateCreateDataToFilter(schema, entity, data, allowUnrecoganized) {
const data2 = {};
for (const attr in data) {
const rel = (0, relation_1.judgeRelation)(schema, entity, attr);
const rel = (0, relation_1.judgeRelation)(schema, entity, attr, allowUnrecoganized);
if (rel === 1) {
// 只需要记住id和各种外键属性不这样处理有些古怪的属性比如coordinate其作为createdata和作为filter并不同构
if (!['geometry', 'geography', 'st_geometry', 'st_point'].includes(schema[entity].attributes[attr]?.type)) {

View File

@ -1,4 +1,3 @@
/// <reference types="node" />
/**
* assert打包体积过大
*/

View File

@ -60,7 +60,7 @@ function destructRelationPath(schema, entity, path, relationFilter, recursive) {
},
filter: relationFilter,
} // as ED['userRelation']['Selection']
}, // as ED[keyof ED]['Selection']['data'],
},
getData: (d) => {
return d.userRelation$entity;
},

View File

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

View File

@ -226,7 +226,7 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
// 此接口将字符串parse成对象再进行初始化
// later表示允许延时状态上下文要处理在时间维度上可能的异常比如用户token已经注销等
abstract initialize(data: any, later?: boolean): Promise<void>;
abstract initialize(data?: any, later?: boolean): Promise<void>;
abstract allowUserUpdate(): boolean;

View File

@ -566,10 +566,10 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict> {
* @returns
*/
const makeCreateFilter = <T2 extends keyof ED>(entity: T2, operation: Omit<ED[T2]['CreateSingle'], 'id'>) => {
const { data, filter } = operation;
const { data, filter, bornAt } = operation;
assert(!(data instanceof Array));
if (data) {
return translateCreateDataToFilter(this.schema, entity, data);
return translateCreateDataToFilter(this.schema, entity, data, !!bornAt);
}
return filter;
};
@ -636,7 +636,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict> {
assert(!(data instanceof Array));
for (const attr in data) {
const rel = judgeRelation(this.schema, entity, attr);
const rel = judgeRelation(this.schema, entity, attr, !!operation.bornAt);
if (rel === 2 && !isModiUpdate) {
assert(root === me && !hasParent, 'cascadeUpdate必须是树结构避免森林');
const mtoOperation = data[attr] as any;

View File

@ -38,7 +38,7 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
private volatileEntities: Array<keyof ED>;
private logger: Logger;
private contextBuilder: (cxtString?: string) => Promise<Cxt>;
private contextBuilder: () => Cxt;
private onVolatileTrigger: <T extends keyof ED>(
entity: T,
trigger: VolatileTrigger<ED, T, Cxt>,
@ -48,7 +48,7 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
) => Promise<void>;
constructor(
contextBuilder: (cxtString?: string) => Promise<Cxt>,
contextBuilder: () => Cxt,
logger: Logger = console,
onVolatileTrigger?: <T extends keyof ED>(
entity: T,
@ -64,15 +64,21 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
this.volatileEntities = [];
this.counter = 0;
this.onVolatileTrigger = onVolatileTrigger || (async (entity, trigger, ids, cxtStr, option) => {
const context = await this.contextBuilder(cxtStr);
await context.begin();
const context = this.contextBuilder();
if (!context.getCurrentTxnId()) {
await context.begin();
}
await context.initialize(JSON.parse(cxtStr));
try {
await this.execVolatileTrigger(entity, trigger.name, ids, context, option);
await context.commit();
}
catch (err) {
await context.rollback();
this.logger.error('error on volatile trigger', entity, trigger.name, ids.join(','), err);
if (!(err instanceof OakMakeSureByMySelfException)) {
this.logger.error('error on volatile trigger', entity, trigger.name, ids.join(','), err);
}
// throw err;
}
});
}
@ -353,8 +359,8 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
}
}
const number = (trigger as CreateTriggerInTxn<ED, T, Cxt>).fn({ operation: operation as ED[T]['Create'] }, context, option as OperateOption);
if (number as number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number as number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
}
assert(commitTriggers.length === 0, `前台不应有commitTrigger`);
@ -376,8 +382,8 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
}
}
const number = await (trigger as CreateTriggerInTxn<ED, T, Cxt>).fn({ operation: operation as ED[T]['Create'] }, context, option as OperateOption);
if (number as number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number as number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
return execPreTrigger(idx + 1);
};
@ -489,8 +495,8 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
operation: operation as ED[T]['Selection'],
result: result!,
}, context, option as SelectOption);
if (number as number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number as number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
}
}
@ -505,8 +511,8 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
operation: operation as ED[T]['Selection'],
result: result!,
}, context, option as SelectOption);
if (number as number > 0) {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
if (number as number > 0 && process.env.NODE_ENV === 'development') {
this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`);
}
return execPostTrigger(idx + 1);
};
@ -551,7 +557,7 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
$lt: timestamp,
}
};
const context = await this.contextBuilder();
const context = this.contextBuilder();
if (context.clusterInfo?.usingCluster) {
const { instanceCount, instanceId } = context.clusterInfo!;
filter.$$seq$$ = {

View File

@ -58,7 +58,7 @@ export function translateCheckerInAsyncContext<
case 'row': {
const { filter, errMsg, inconsistentRows } = checker;
const fn = (async ({ operation }, context, option) => {
const { filter: operationFilter, data, action } = operation;
const { filter: operationFilter, data, action, bornAt } = operation;
const filter2 = typeof filter === 'function' ? await filter(operation, context, option) : filter;
if (['select', 'count', 'stat'].includes(action)) {
operation.filter = combineFilters(entity, context.getSchema(), [operationFilter, filter2]);
@ -102,11 +102,11 @@ export function translateCheckerInAsyncContext<
assert(data);
if (data instanceof Array) {
for (const d of <ED[T]['CreateMulti']['data']>data) {
await checkSingle(translateCreateDataToFilter(schema, entity, d))
await checkSingle(translateCreateDataToFilter(schema, entity, d, !!bornAt))
}
}
else {
await checkSingle(translateCreateDataToFilter(schema, entity, <ED[T]['CreateSingle']['data']><unknown>data))
await checkSingle(translateCreateDataToFilter(schema, entity, <ED[T]['CreateSingle']['data']><unknown>data, !!bornAt))
}
return;
}
@ -160,7 +160,7 @@ export function translateCheckerInSyncContext<
case 'row': {
const { filter, errMsg, entity } = checker;
const fn = (operation: ED[T]['Operation'], context: Cxt, option: OperateOption | SelectOption) => {
const { filter: operationFilter, data, action } = operation;
const { filter: operationFilter, data, action, bornAt } = operation;
const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter;
let operationFilter2 = operationFilter;
if (action === 'create') {
@ -168,7 +168,7 @@ export function translateCheckerInSyncContext<
// 前端的策略是有data用data无data用filter
// 目前前端应该不可能制造出来createMultiple
assert(!(data instanceof Array));
operationFilter2 = translateCreateDataToFilter(schema, entity, data as ED[T]['CreateSingle']['data']);
operationFilter2 = translateCreateDataToFilter(schema, entity, data as ED[T]['CreateSingle']['data'], !!bornAt);
}
}
assert(!(filter2 instanceof Promise));

View File

@ -10,10 +10,11 @@ export function translateCreateDataToFilter<ED extends EntityDict & BaseEntityDi
schema: StorageSchema<ED>,
entity: T,
data: ED[T]['CreateSingle']['data'],
allowUnrecoganized: boolean,
) {
const data2: ED[T]['Selection']['filter'] = {};
for (const attr in data) {
const rel = judgeRelation(schema, entity, attr);
const rel = judgeRelation(schema, entity, attr, allowUnrecoganized);
if (rel === 1) {
// 只需要记住id和各种外键属性不这样处理有些古怪的属性比如coordinate其作为createdata和作为filter并不同构
if (!['geometry', 'geography', 'st_geometry', 'st_point'].includes(schema[entity].attributes[attr as any]?.type!)) {