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

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) {
const injectDataIndexFileDependencies = [];
const injectDataIndexFileBriefNames = [];
const webDir = join(cwd, 'web');
if (!(0, fs_1.existsSync)(webDir)) {
// 如果没有web目录说明是module不需要处理模块级别的文件注入
return;
}
dependencies.forEach((dep, idx) => {
const moduleDir = join(cwd, 'node_modules', dep);
const moduleTemplateDir = join(moduleDir, 'template');
@ -791,12 +786,47 @@ function tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, 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
* 这些和dependency相关的项目文件
*/
function buildDependency(rebuild) {
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');
if (!(0, fs_1.existsSync)(depConfigFile)) {
console.error(`${depConfigFile}不存在,无法构建启动文件`);
@ -816,26 +846,31 @@ function buildDependency(rebuild) {
];
const program = ts.createProgram(templateFileList, {});
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const initDevFile = join(cwd, 'src', 'initialize.dev.ts');
if ((0, fs_1.existsSync)(initDevFile) && !rebuild) {
console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`);
if (!isModule) {
const initDevFile = join(cwd, 'src', 'initialize.dev.ts');
if ((0, fs_1.existsSync)(initDevFile) && !rebuild) {
console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeDev(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[0]), printer, initDevFile);
}
const initProdFile = join(cwd, 'src', 'initialize.prod.ts');
if ((0, fs_1.existsSync)(initProdFile) && !rebuild) {
console.log(`[${initProdFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeProd(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[1]), printer, initProdFile);
}
const initFeaturesFile = join(cwd, 'src', 'initializeFeatures.ts');
if ((0, fs_1.existsSync)(initFeaturesFile) && !rebuild) {
console.log(`[${initFeaturesFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2]), printer, initFeaturesFile);
}
}
else {
outputIntializeDev(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[0]), printer, initDevFile);
}
const initProdFile = join(cwd, 'src', 'initialize.prod.ts');
if ((0, fs_1.existsSync)(initProdFile) && !rebuild) {
console.log(`[${initProdFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeProd(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[1]), printer, initProdFile);
}
const initFeaturesFile = join(cwd, 'src', 'initializeFeatures.ts');
if ((0, fs_1.existsSync)(initFeaturesFile) && !rebuild) {
console.log(`[${initFeaturesFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2]), printer, initFeaturesFile);
injectInitializeToFeatureIndex(cwd, dependencies, briefNames, printer);
}
const dependentContextFile = join(cwd, 'src', 'context', 'DependentContext.ts');
if ((0, fs_1.existsSync)(dependentContextFile) && !rebuild) {
@ -873,6 +908,8 @@ function buildDependency(rebuild) {
outputFeatureIndex(dependencies, briefNames, program.getSourceFile(templateFileList[6]), printer, featureIndexFile);
}
// 把各个依赖项目的一些初始化的文件拷贝过去
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer);
if (!isModule) {
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer);
}
}
exports.default = buildDependency;

View File

@ -14,8 +14,8 @@ export declare abstract class AsyncContext<ED extends EntityDict> implements Con
opResult: OperationResult<ED>;
private message?;
events: {
commit: Array<() => Promise<void>>;
rollback: Array<() => Promise<void>>;
commit: Array<(records: OpRecord<ED>[], cxtStr: string) => 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;
setScene(scene?: string): void;
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;
/**
* context中不应该有并发的事务使

View File

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

View File

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

View File

@ -1305,7 +1305,7 @@ function outputIntializeFeatures(
function injectDataIndexFile(
dataIndexFile: 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);
@ -1411,12 +1411,6 @@ function tryCopyModuleTemplateFiles(
const injectDataIndexFileDependencies: string[] = [];
const injectDataIndexFileBriefNames: string[] = [];
const webDir = join(cwd, 'web');
if (!existsSync(webDir)) {
// 如果没有web目录说明是module不需要处理模块级别的文件注入
return;
}
dependencies.forEach(
(dep, idx) => {
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
* dependency相关的项目文件
@ -1470,6 +1579,10 @@ function tryCopyModuleTemplateFiles(
export default function buildDependency(rebuild?: boolean) {
const cwd = process.cwd();
const webDir = join(cwd, 'web');
const isModule = !existsSync(webDir); // 如果没有web目录说明是module不需要处理模块级别的文件注入
const depConfigFile = join(cwd, 'src', 'configuration', 'dependency.ts');
if (!existsSync(depConfigFile)) {
console.error(`${depConfigFile}不存在,无法构建启动文件`);
@ -1496,30 +1609,34 @@ export default function buildDependency(rebuild?: boolean) {
const program = ts.createProgram(templateFileList, {});
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
if (!isModule) {
const initDevFile = join(cwd, 'src', 'initialize.dev.ts');
if (existsSync(initDevFile) && !rebuild) {
console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeDev(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[0])!, printer, initDevFile);
}
const initDevFile = join(cwd, 'src', 'initialize.dev.ts');
if (existsSync(initDevFile) && !rebuild) {
console.log(`[${initDevFile}]文件已经存在,无需构建启动文件`);
const initProdFile = join(cwd, 'src', 'initialize.prod.ts');
if (existsSync(initProdFile) && !rebuild) {
console.log(`[${initProdFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeProd(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[1])!, printer, initProdFile);
}
const initFeaturesFile = join(cwd, 'src', 'initializeFeatures.ts');
if (existsSync(initFeaturesFile) && !rebuild) {
console.log(`[${initFeaturesFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2])!, printer, initFeaturesFile);
}
}
else {
outputIntializeDev(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[0])!, printer, initDevFile);
}
const initProdFile = join(cwd, 'src', 'initialize.prod.ts');
if (existsSync(initProdFile) && !rebuild) {
console.log(`[${initProdFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeProd(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[1])!, printer, initProdFile);
}
const initFeaturesFile = join(cwd, 'src', 'initializeFeatures.ts');
if (existsSync(initFeaturesFile) && !rebuild) {
console.log(`[${initFeaturesFile}]文件已经存在,无需构建启动文件`);
}
else {
outputIntializeFeatures(cwd, dependencies, briefNames, program.getSourceFile(templateFileList[2])!, printer, initFeaturesFile);
injectInitializeToFeatureIndex(cwd, dependencies, briefNames, printer);
}
const dependentContextFile = join(cwd, 'src', 'context', 'DependentContext.ts');
@ -1563,5 +1680,7 @@ export default function buildDependency(rebuild?: boolean) {
}
// 把各个依赖项目的一些初始化的文件拷贝过去
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer);
if (!isModule) {
tryCopyModuleTemplateFiles(cwd, dependencies, briefNames, printer);
}
}

View File

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

View File

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