事务提交时的一些细节性处理

This commit is contained in:
Xu Chang 2024-04-21 15:40:13 +08:00
parent 021774a154
commit c5ee23175e
7 changed files with 252 additions and 81 deletions

View File

@ -748,11 +748,6 @@ function tryCopyPages(cwdPageDir, modulePageDir) {
function tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer) { function tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer) {
const injectDataIndexFileDependencies = []; const injectDataIndexFileDependencies = [];
const injectDataIndexFileBriefNames = []; const injectDataIndexFileBriefNames = [];
const webDir = join(cwd, 'web');
if (!(0, fs_1.existsSync)(webDir)) {
// 如果没有web目录说明是module不需要处理模块级别的文件注入
return;
}
dependencies.forEach((dep, idx) => { dependencies.forEach((dep, idx) => {
const moduleDir = join(cwd, 'node_modules', dep); const moduleDir = join(cwd, 'node_modules', dep);
const moduleTemplateDir = join(moduleDir, 'template'); const moduleTemplateDir = join(moduleDir, 'template');
@ -791,12 +786,47 @@ function tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer) {
injectDataIndexFile(join(cwd, 'src', 'data', 'index.ts'), injectDataIndexFileBriefNames, printer); injectDataIndexFile(join(cwd, 'src', 'data', 'index.ts'), injectDataIndexFileBriefNames, printer);
} }
} }
/**
* 对于module类型的项目在feature/index.ts中注入initialize函数
* @param cwd
* @param dependencies
* @param briefNames
*/
function injectInitializeToFeatureIndex(cwd, dependencies, briefNames, printer) {
const featureIndexFile = join(cwd, 'src', 'features', 'index.ts');
const sourceFile = ts.createSourceFile('index.ts', (0, fs_1.readFileSync)(featureIndexFile, 'utf-8'), ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
const { statements } = sourceFile;
const initializeStmt = statements.find((stmt) => ts.isFunctionDeclaration(stmt) && ts.isIdentifier(stmt.name) && stmt.name.text === 'initialize');
if (!initializeStmt) {
const statements2 = [
...(dependencies.map((dep, idx) => factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("FeatureDict"), factory.createIdentifier(`${(0, string_1.firstLetterUpperCase)(briefNames[idx])}FeatureDict`))])), factory.createStringLiteral(dep), undefined))),
...statements,
factory.createFunctionDeclaration([
factory.createToken(ts.SyntaxKind.ExportKeyword),
factory.createToken(ts.SyntaxKind.AsyncKeyword)
], undefined, factory.createIdentifier("initialize"), [
factory.createTypeParameterDeclaration(undefined, factory.createIdentifier("ED"), factory.createTypeReferenceNode(factory.createIdentifier("EntityDict"), undefined), undefined)
], [
factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier("features"), undefined, factory.createIntersectionTypeNode([
factory.createTypeReferenceNode(factory.createIdentifier("FeatureDict"), [factory.createTypeReferenceNode(factory.createIdentifier("ED"), undefined)]),
factory.createTypeReferenceNode(factory.createIdentifier("BasicFeatures"), [factory.createTypeReferenceNode(factory.createIdentifier("ED"), undefined)]),
...briefNames.map((ele) => factory.createTypeReferenceNode(factory.createIdentifier(`${(0, string_1.firstLetterUpperCase)(ele)}FeatureDict`), [factory.createTypeReferenceNode(factory.createIdentifier("ED"), undefined)]))
]), undefined)
], undefined, factory.createBlock([], true))
];
const result = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(statements2), sourceFile);
(0, fs_1.writeFileSync)(featureIndexFile, result, { flag: 'w' });
console.log(`注入${featureIndexFile}文件成功用户可以自己修正initialize函数的参数和逻辑`);
}
}
/** /**
* 本函数用于构建src/initialize.dev, src/initialize.prod, src/initializeFeatures, src/context/FrontendContext, src/contextBackendContext * 本函数用于构建src/initialize.dev, src/initialize.prod, src/initializeFeatures, src/context/FrontendContext, src/contextBackendContext
* 这些和dependency相关的项目文件 * 这些和dependency相关的项目文件
*/ */
function buildDependency(rebuild) { function buildDependency(rebuild) {
const cwd = process.cwd(); const cwd = process.cwd();
const webDir = join(cwd, 'web');
const isModule = !(0, fs_1.existsSync)(webDir); // 如果没有web目录说明是module不需要处理模块级别的文件注入
const depConfigFile = join(cwd, 'src', 'configuration', 'dependency.ts'); const depConfigFile = join(cwd, 'src', 'configuration', 'dependency.ts');
if (!(0, fs_1.existsSync)(depConfigFile)) { if (!(0, fs_1.existsSync)(depConfigFile)) {
console.error(`${depConfigFile}不存在,无法构建启动文件`); console.error(`${depConfigFile}不存在,无法构建启动文件`);
@ -816,6 +846,7 @@ function buildDependency(rebuild) {
]; ];
const program = ts.createProgram(templateFileList, {}); const program = ts.createProgram(templateFileList, {});
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
if (!isModule) {
const initDevFile = join(cwd, 'src', 'initialize.dev.ts'); const initDevFile = join(cwd, 'src', 'initialize.dev.ts');
if ((0, fs_1.existsSync)(initDevFile) && !rebuild) { if ((0, fs_1.existsSync)(initDevFile) && !rebuild) {
console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`); console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`);
@ -837,6 +868,10 @@ function buildDependency(rebuild) {
else { else {
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2]), printer, initFeaturesFile); outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2]), printer, initFeaturesFile);
} }
}
else {
injectInitializeToFeatureIndex(cwd, dependencies, briefNames, printer);
}
const dependentContextFile = join(cwd, 'src', 'context', 'DependentContext.ts'); const dependentContextFile = join(cwd, 'src', 'context', 'DependentContext.ts');
if ((0, fs_1.existsSync)(dependentContextFile) && !rebuild) { if ((0, fs_1.existsSync)(dependentContextFile) && !rebuild) {
console.log(`[${dependentContextFile}]文件已经存在,无需构建启动文件`); console.log(`[${dependentContextFile}]文件已经存在,无需构建启动文件`);
@ -873,6 +908,8 @@ function buildDependency(rebuild) {
outputFeatureIndex(dependencies, briefNames, program.getSourceFile(templateFileList[6]), printer, featureIndexFile); outputFeatureIndex(dependencies, briefNames, program.getSourceFile(templateFileList[6]), printer, featureIndexFile);
} }
// 把各个依赖项目的一些初始化的文件拷贝过去 // 把各个依赖项目的一些初始化的文件拷贝过去
if (!isModule) {
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer); tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer);
} }
}
exports.default = buildDependency; exports.default = buildDependency;

View File

@ -14,8 +14,8 @@ export declare abstract class AsyncContext<ED extends EntityDict> implements Con
opResult: OperationResult<ED>; opResult: OperationResult<ED>;
private message?; private message?;
events: { events: {
commit: Array<() => Promise<void>>; commit: Array<(records: OpRecord<ED>[], cxtStr: string) => Promise<void>>;
rollback: Array<() => Promise<void>>; rollback: Array<(records: OpRecord<ED>[], cxtStr: string) => Promise<void>>;
}; };
/** /**
* *
@ -27,7 +27,7 @@ export declare abstract class AsyncContext<ED extends EntityDict> implements Con
getScene(): string | undefined; getScene(): string | undefined;
setScene(scene?: string): void; setScene(scene?: string): void;
private resetEvents; private resetEvents;
on(event: 'commit' | 'rollback', callback: () => Promise<void>): void; on(event: 'commit' | 'rollback', callback: (records: OpRecord<ED>[], cxtStr: string) => Promise<void>): void;
saveOpRecord<T extends keyof ED>(entity: T, operation: ED[T]['Operation']): void; saveOpRecord<T extends keyof ED>(entity: T, operation: ED[T]['Operation']): void;
/** /**
* context中不应该有并发的事务使 * context中不应该有并发的事务使

View File

@ -123,7 +123,8 @@ class AsyncContext {
await e(); await e();
} */ } */
// 提交时不能等在跨事务trigger上 // 提交时不能等在跨事务trigger上
commitEvents.forEach(evt => evt()); const cxtStr = await this.toString();
commitEvents.forEach(evt => evt(this.opRecords, cxtStr));
this.uuid = undefined; this.uuid = undefined;
this.resetEvents(); this.resetEvents();
this.opRecords = []; this.opRecords = [];
@ -135,9 +136,9 @@ class AsyncContext {
if (this.uuid) { if (this.uuid) {
await this.rowStore.rollback(this.uuid); await this.rowStore.rollback(this.uuid);
const { rollback: rollbackEvents } = this.events; const { rollback: rollbackEvents } = this.events;
for (const e of rollbackEvents) { // 回退时不能等在跨事务trigger上
await e(); const cxtStr = await this.toString();
} rollbackEvents.forEach(evt => evt(this.opRecords, cxtStr));
this.uuid = undefined; this.uuid = undefined;
this.opRecords = []; this.opRecords = [];
this.opResult = {}; this.opResult = {};

View File

@ -221,19 +221,22 @@ class TriggerExecutor {
} }
} }
postCommitTrigger(entity, operation, trigger, context, option) { postCommitTrigger(entity, operation, trigger, context, option) {
context.on('commit', async () => { context.on('commit', async (opRecords, cxtStr) => {
let ids = []; let ids = [];
let cxtStr = await context.toString(); let cxtStr2 = cxtStr;
const { opRecords } = context;
const { data } = operation; const { data } = operation;
if (operation.action === 'create') { if (operation.action === 'create') {
if (data instanceof Array) { if (data instanceof Array) {
ids = data.map(ele => ele.id); ids = data.map(ele => ele.id);
cxtStr = data[0].$$triggerData$$?.cxtStr || await context.toString(); if (data[0].$$triggerData$$?.cxtStr) {
cxtStr2 = data[0].$$triggerData$$?.cxtStr;
}
} }
else { else {
ids = [data.id]; ids = [data.id];
cxtStr = data.$$triggerData$$?.cxtStr || await context.toString(); if (data.$$triggerData$$?.cxtStr) {
cxtStr2 = data.$$triggerData$$?.cxtStr;
}
} }
} }
else { else {
@ -242,7 +245,9 @@ class TriggerExecutor {
* 若trigger是takeEasy只会在事务提交时做一次使用当前context应也无大问题 * 若trigger是takeEasy只会在事务提交时做一次使用当前context应也无大问题
* 暂时先这样设计若当前提交事务中改变了cxt内容也许会有问题by Xc 20240319 * 暂时先这样设计若当前提交事务中改变了cxt内容也许会有问题by Xc 20240319
*/ */
cxtStr = data.$$triggerData$$?.cxtStr || await context.toString(); if (data.$$triggerData$$?.cxtStr) {
cxtStr2 = data.$$triggerData$$?.cxtStr;
}
const record = opRecords.find(ele => ele.id === operation.id); const record = opRecords.find(ele => ele.id === operation.id);
// 目前框架在operation时一定会将ids记录在operation当中见CascadeStore中的doUpdateSingleRowAsync函数 // 目前框架在operation时一定会将ids记录在operation当中见CascadeStore中的doUpdateSingleRowAsync函数
(0, assert_1.default)(record && record.a !== 'c'); (0, assert_1.default)(record && record.a !== 'c');
@ -250,7 +255,7 @@ class TriggerExecutor {
ids = f.id.$in; ids = f.id.$in;
} }
// 此时项目的上下文和执行此trigger时的上下文可能不一致rootMode采用当时的上下文cxtStr来执行 // 此时项目的上下文和执行此trigger时的上下文可能不一致rootMode采用当时的上下文cxtStr来执行
this.onVolatileTrigger(entity, trigger, ids, cxtStr, option); this.onVolatileTrigger(entity, trigger, ids, cxtStr2, option);
}); });
} }
preOperation(entity, operation, context, option) { preOperation(entity, operation, context, option) {

View File

@ -1305,7 +1305,7 @@ function outputIntializeFeatures(
function injectDataIndexFile( function injectDataIndexFile(
dataIndexFile: string, dataIndexFile: string,
briefNames: string[], briefNames: string[],
printer: ts.Printer, printer: ts.Printer
) { ) {
const sourceFile = ts.createSourceFile('index.ts', readFileSync(dataIndexFile, 'utf-8'), ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); const sourceFile = ts.createSourceFile('index.ts', readFileSync(dataIndexFile, 'utf-8'), ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
@ -1411,12 +1411,6 @@ function tryCopyModuleTemplateFiles(
const injectDataIndexFileDependencies: string[] = []; const injectDataIndexFileDependencies: string[] = [];
const injectDataIndexFileBriefNames: string[] = []; const injectDataIndexFileBriefNames: string[] = [];
const webDir = join(cwd, 'web');
if (!existsSync(webDir)) {
// 如果没有web目录说明是module不需要处理模块级别的文件注入
return;
}
dependencies.forEach( dependencies.forEach(
(dep, idx) => { (dep, idx) => {
const moduleDir = join(cwd, 'node_modules', dep); const moduleDir = join(cwd, 'node_modules', dep);
@ -1463,6 +1457,121 @@ function tryCopyModuleTemplateFiles(
} }
} }
/**
* module类型的项目feature/index.ts中注入initialize函数
* @param cwd
* @param dependencies
* @param briefNames
*/
function injectInitializeToFeatureIndex(
cwd: string,
dependencies: string[],
briefNames: string[],
printer: ts.Printer,
) {
const featureIndexFile = join(cwd, 'src', 'features', 'index.ts');
const sourceFile = ts.createSourceFile('index.ts', readFileSync(featureIndexFile, 'utf-8'), ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
const { statements } = sourceFile;
const initializeStmt = statements.find(
(stmt) => ts.isFunctionDeclaration(stmt) && ts.isIdentifier(stmt.name!) && stmt.name.text === 'initialize'
);
if (!initializeStmt) {
const statements2 = [
...(
dependencies.map(
(dep, idx) => factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports(
[factory.createImportSpecifier(
false,
factory.createIdentifier("FeatureDict"),
factory.createIdentifier(`${firstLetterUpperCase(briefNames[idx])}FeatureDict`)
)]
)
),
factory.createStringLiteral(dep),
undefined
)
)
),
...statements,
factory.createFunctionDeclaration(
[
factory.createToken(ts.SyntaxKind.ExportKeyword),
factory.createToken(ts.SyntaxKind.AsyncKeyword)
],
undefined,
factory.createIdentifier("initialize"),
[
factory.createTypeParameterDeclaration(
undefined,
factory.createIdentifier("ED"),
factory.createTypeReferenceNode(
factory.createIdentifier("EntityDict"),
undefined
),
undefined
)
],
[
factory.createParameterDeclaration(
undefined,
undefined,
factory.createIdentifier("features"),
undefined,
factory.createIntersectionTypeNode([
factory.createTypeReferenceNode(
factory.createIdentifier("FeatureDict"),
[factory.createTypeReferenceNode(
factory.createIdentifier("ED"),
undefined
)]
),
factory.createTypeReferenceNode(
factory.createIdentifier("BasicFeatures"),
[factory.createTypeReferenceNode(
factory.createIdentifier("ED"),
undefined
)]
),
...briefNames.map(
(ele) => factory.createTypeReferenceNode(
factory.createIdentifier(`${firstLetterUpperCase(ele)}FeatureDict`),
[factory.createTypeReferenceNode(
factory.createIdentifier("ED"),
undefined
)]
)
)
]),
undefined
)
],
undefined,
factory.createBlock(
[],
true
)
)
];
const result = printer.printList(
ts.ListFormat.SourceFileStatements,
factory.createNodeArray(statements2),
sourceFile);
writeFileSync(featureIndexFile, result, { flag: 'w' });
console.log(`注入${featureIndexFile}文件成功用户可以自己修正initialize函数的参数和逻辑`);
}
}
/** /**
* src/initialize.dev, src/initialize.prod, src/initializeFeatures, src/context/FrontendContext, src/contextBackendContext * src/initialize.dev, src/initialize.prod, src/initializeFeatures, src/context/FrontendContext, src/contextBackendContext
* dependency相关的项目文件 * dependency相关的项目文件
@ -1470,6 +1579,10 @@ function tryCopyModuleTemplateFiles(
export default function buildDependency(rebuild?: boolean) { export default function buildDependency(rebuild?: boolean) {
const cwd = process.cwd(); const cwd = process.cwd();
const webDir = join(cwd, 'web');
const isModule = !existsSync(webDir); // 如果没有web目录说明是module不需要处理模块级别的文件注入
const depConfigFile = join(cwd, 'src', 'configuration', 'dependency.ts'); const depConfigFile = join(cwd, 'src', 'configuration', 'dependency.ts');
if (!existsSync(depConfigFile)) { if (!existsSync(depConfigFile)) {
console.error(`${depConfigFile}不存在,无法构建启动文件`); console.error(`${depConfigFile}不存在,无法构建启动文件`);
@ -1496,7 +1609,7 @@ export default function buildDependency(rebuild?: boolean) {
const program = ts.createProgram(templateFileList, {}); const program = ts.createProgram(templateFileList, {});
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
if (!isModule) {
const initDevFile = join(cwd, 'src', 'initialize.dev.ts'); const initDevFile = join(cwd, 'src', 'initialize.dev.ts');
if (existsSync(initDevFile) && !rebuild) { if (existsSync(initDevFile) && !rebuild) {
console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`); console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`);
@ -1521,6 +1634,10 @@ export default function buildDependency(rebuild?: boolean) {
else { else {
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2])!, printer, initFeaturesFile); outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2])!, printer, initFeaturesFile);
} }
}
else {
injectInitializeToFeatureIndex(cwd, dependencies, briefNames, printer);
}
const dependentContextFile = join(cwd, 'src', 'context', 'DependentContext.ts'); const dependentContextFile = join(cwd, 'src', 'context', 'DependentContext.ts');
if (existsSync(dependentContextFile) && !rebuild) { if (existsSync(dependentContextFile) && !rebuild) {
@ -1563,5 +1680,7 @@ export default function buildDependency(rebuild?: boolean) {
} }
// 把各个依赖项目的一些初始化的文件拷贝过去 // 把各个依赖项目的一些初始化的文件拷贝过去
if (!isModule) {
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer); tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer);
} }
}

View File

@ -17,8 +17,8 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
opResult: OperationResult<ED>; opResult: OperationResult<ED>;
private message?: string; private message?: string;
events: { events: {
commit: Array<() => Promise<void>>; commit: Array<(records: OpRecord<ED>[], cxtStr: string) => Promise<void>>;
rollback: Array<() => Promise<void>>; rollback: Array<(records: OpRecord<ED>[], cxtStr: string) => Promise<void>>;
} }
/** /**
@ -82,7 +82,7 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
}; };
} }
on(event: 'commit' | 'rollback', callback: () => Promise<void>): void { on(event: 'commit' | 'rollback', callback: (records: OpRecord<ED>[], cxtStr: string) => Promise<void>): void {
this.uuid && this.events[event].push(callback); this.uuid && this.events[event].push(callback);
} }
@ -141,8 +141,9 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
} */ } */
// 提交时不能等在跨事务trigger上 // 提交时不能等在跨事务trigger上
const cxtStr = await this.toString();
commitEvents.forEach( commitEvents.forEach(
evt => evt() evt => evt(this.opRecords, cxtStr)
); );
this.uuid = undefined; this.uuid = undefined;
this.resetEvents(); this.resetEvents();
@ -155,9 +156,12 @@ export abstract class AsyncContext<ED extends EntityDict> implements Context {
if (this.uuid) { if (this.uuid) {
await this.rowStore.rollback(this.uuid!); await this.rowStore.rollback(this.uuid!);
const { rollback: rollbackEvents } = this.events; const { rollback: rollbackEvents } = this.events;
for (const e of rollbackEvents) {
await e(); // 回退时不能等在跨事务trigger上
} const cxtStr = await this.toString();
rollbackEvents.forEach(
evt => evt(this.opRecords, cxtStr)
);
this.uuid = undefined; this.uuid = undefined;
this.opRecords = []; this.opRecords = [];
this.opResult = {}; this.opResult = {};

View File

@ -287,19 +287,22 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
context: Cxt, context: Cxt,
option: OperateOption option: OperateOption
) { ) {
context.on('commit', async () => { context.on('commit', async (opRecords, cxtStr) => {
let ids = [] as string[]; let ids = [] as string[];
let cxtStr = await context.toString(); let cxtStr2 = cxtStr;
const { opRecords } = context;
const { data } = operation; const { data } = operation;
if (operation.action === 'create') { if (operation.action === 'create') {
if (data instanceof Array) { if (data instanceof Array) {
ids = data.map(ele => ele.id!); ids = data.map(ele => ele.id!);
cxtStr = data[0].$$triggerData$$?.cxtStr || await context.toString(); if (data[0].$$triggerData$$?.cxtStr) {
cxtStr2 = data[0].$$triggerData$$?.cxtStr;
}
} }
else { else {
ids = [data.id!]; ids = [data.id!];
cxtStr = data.$$triggerData$$?.cxtStr || await context.toString(); if (data.$$triggerData$$?.cxtStr) {
cxtStr2 = data.$$triggerData$$?.cxtStr;
}
} }
} }
else { else {
@ -308,7 +311,9 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
* trigger是takeEasy使context应也无大问题 * trigger是takeEasy使context应也无大问题
* cxt内容by Xc 20240319 * cxt内容by Xc 20240319
*/ */
cxtStr = (<ED[T]['Update']['data']>data).$$triggerData$$?.cxtStr || await context.toString(); if ((<ED[T]['Update']['data']>data).$$triggerData$$?.cxtStr) {
cxtStr2 = (<ED[T]['Update']['data']>data).$$triggerData$$?.cxtStr;
}
const record = opRecords.find( const record = opRecords.find(
ele => (ele as CreateOpResult<ED, keyof ED>).id === operation.id, ele => (ele as CreateOpResult<ED, keyof ED>).id === operation.id,
); );
@ -318,7 +323,7 @@ export class TriggerExecutor<ED extends EntityDict & BaseEntityDict, Cxt extends
ids = f!.id!.$in; ids = f!.id!.$in;
} }
// 此时项目的上下文和执行此trigger时的上下文可能不一致rootMode采用当时的上下文cxtStr来执行 // 此时项目的上下文和执行此trigger时的上下文可能不一致rootMode采用当时的上下文cxtStr来执行
this.onVolatileTrigger(entity, trigger, ids, cxtStr, option); this.onVolatileTrigger(entity, trigger, ids, cxtStr2, option);
}); });
} }