Skip to content

Commit

Permalink
feat: add AST automatic adding cqrs message handler decorator (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mararok authored Oct 5, 2024
1 parent a13918a commit da4a94d
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 20 deletions.
1 change: 1 addition & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const jestConfig: JestConfigWithTsJest = {
rootDir,
tsconfig: compilerOptions,
diagnostics: false,
isolatedModules: true,
}]
},
testMatch: ["<rootDir>/test/**/*.test.ts"],
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Jest/HcJestTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class HcJestTransformer implements AsyncTransformer<HcJestTransformerOpti
const outSourceFile = transformed.transformed[0];

const printed = TsTransfromerHelper.printFile(outSourceFile);
const tmpPath = this.tmpDir + '/' + featureSourcePath.featureName+ '-' + hash('md5', featureSourcePath.sourcePath, 'hex').substring(0, 8) + '-' + path.basename(featureSourcePath.sourcePath);
const tmpPath = this.tmpDir + '/' + featureSourcePath.featureName+ '-' + hash('md5', featureSourcePath.sourcePath, 'hex').substring(0, 12) + '-' + path.basename(featureSourcePath.sourcePath);
writeFileSync(tmpPath, printed);

const outTranspile = ts.transpileModule(printed, {
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Jest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ export default {
}

return cachedTransformer;
},
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { FeatureApplicationCommandMeta, type FeatureApplicationQueryMeta } from '../../../Util/Feature/Meta';
import ts from "typescript";
import { type FeatureMeta } from "../../../Util/Feature/Meta";
import { AbstractFeatureTsTransformer } from "./AbstractFeatureTsTransformer";
import type { FeatureTransformContext } from "./FeatureTransformContext";
import type { FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer";
import { ImportDeclarationWrapper } from "../Helper/ImportDeclarationWrapper";

interface VisitContext extends FeatureTransformContext {
meta: FeatureApplicationCommandMeta | FeatureApplicationQueryMeta;
handlerDecoratorName: string;
cqrsImport: ImportDeclarationWrapper;
}

export class FeatureApplicationMessageHandlerTsTransformer extends AbstractFeatureTsTransformer {

public supports(featureSourcePath: FeatureSourcePath, feature: FeatureMeta): boolean {
const meta = feature.applicationCqrsHandlerMap.get(featureSourcePath.localSourcePath);
return !!meta;
}

public transform(source: ts.SourceFile, context: FeatureTransformContext): ts.SourceFile {
const messageMeta = context.feature.applicationCqrsHandlerMap.get(context.featureSourcePath.localSourcePath)!;
const handlerDecoratorName = messageMeta instanceof FeatureApplicationCommandMeta ? "CommandHandler" : "QueryHandler";
const cqrsImport = ImportDeclarationWrapper.create(handlerDecoratorName, "@nestjs/cqrs", context.tsContext);
const visitContext: VisitContext = {
...context,
meta: messageMeta,
handlerDecoratorName: handlerDecoratorName,
cqrsImport
};

const transformed = ts.factory.updateSourceFile(source, [
cqrsImport.declaration,
...ts.visitNodes(
source.statements,
this.visitSourceStatement.bind(this, visitContext),
(node): node is ts.Statement => ts.isStatement(node)
)
]);

return transformed;
}

private visitSourceStatement(visitContext: VisitContext, node: ts.Statement) {
if (ts.isClassDeclaration(node)) {
const handlerDecorator = ts.factory.createDecorator(ts.factory.createCallExpression(
visitContext.cqrsImport.get(visitContext.handlerDecoratorName),
undefined,
[ts.factory.createIdentifier(visitContext.meta.className)]
));
return ts.factory.updateClassDeclaration(
node,
[handlerDecorator, ...node.modifiers ?? []],
node.name,
node.typeParameters,
node.heritageClauses,
node.members
);
}

return node;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FeatureMeta } from "../../../Util/Feature/Meta/FeatureMeta";
import type { FeatureSourcePath } from "../../../Util/Feature/FeatureModuleDiscoverer";

export interface FeatureTransformContext {
source: ts.SourceFile;
featureSourcePath: FeatureSourcePath;
feature: FeatureMeta;
tsContext: ts.TransformationContext;
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Transformer/Feature/FeatureTsTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TsImportHelper } from "../TsImportHelper";
import { ModuleClassTsTransformer } from "../ModuleClassTsTransformer";
import type { FeatureTransformContext } from "./FeatureTransformContext";
import { HObjectTsTransformer } from "./HObject/HObjectTsTransformer";
import { FeatureApplicationMessageHandlerTsTransformer } from "./FeatureApplicationMessageHandlerTsTransformer";


/**
Expand All @@ -37,6 +38,7 @@ export class FeatureTsTransformer {
new FeatureInfraDomainModuleTsTransformer(helpers),
new FeatureModuleTsTransformer(helpers),
new HObjectTsTransformer(helpers),
new FeatureApplicationMessageHandlerTsTransformer(helpers)
];
}

Expand Down Expand Up @@ -89,6 +91,7 @@ export class FeatureTsTransformer {

const feature = this.features.get(featureSourcePath.featureName)!;
return {
source,
feature,
featureSourcePath,
tsContext: context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { HObjectToJSONTsFactory } from "./HObjectToJSONTsFactory";
import type { FeatureSourcePath } from "../../../../Util/Feature/FeatureModuleDiscoverer";
import { HObjectToConstructorTsFactory } from "./HObjectConstructorTsFactory";
import { ImportDeclarationWrapper } from "../../Helper/ImportDeclarationWrapper";
import { HObjectKind, type FeatureHObjectMeta } from "@/Util/Feature/Meta";
import { HObjectKind, type FeatureHObjectMeta } from "../../../../Util/Feature/Meta";

interface VisitContext extends FeatureTransformContext {
source: ts.SourceFile;
Expand Down
14 changes: 13 additions & 1 deletion src/Util/Feature/Meta/FeatureApplicationMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class FeatureApplicationCommandMeta implements FeatureApplicationMessageM
return this.path + '/' + this.className + '.ts';
}

public get handlerFilePath(): string {
return this.path + '/' + this.handlerClass + '.ts';
}

public get kind(): HObjectKind.Command {
return HObjectKind.Command;
}
Expand Down Expand Up @@ -73,6 +77,10 @@ export class FeatureApplicationQueryMeta implements FeatureApplicationMessageMet
return this.path + '/' + this.className + '.ts';
}

public get handlerFilePath(): string {
return this.path + '/' + this.handlerClass + '.ts';
}

public get kind(): HObjectKind.Query {
return HObjectKind.Query;
}
Expand Down Expand Up @@ -126,6 +134,8 @@ export class FeatureApplicationServiceMeta implements FeatureClassMeta {
}
}

export type FeatureApplicationCqrsHandlerMap = Map<string, FeatureApplicationCommandMeta | FeatureApplicationQueryMeta>;

export class FeatureApplicationMeta implements JsonSerialize {

public constructor(
Expand All @@ -151,13 +161,15 @@ export class FeatureApplicationMeta implements JsonSerialize {
return new this(commands, queries, dtos, services);
}

public collectHObjects(map: FeatureHObjectMap): void {
public collectHObjects(map: FeatureHObjectMap, handlerMap: FeatureApplicationCqrsHandlerMap): void {
for (const i of this.commands) {
map.set(i.filePath, i);
handlerMap.set(i.handlerFilePath, i);
}

for (const i of this.queries) {
map.set(i.filePath, i);
handlerMap.set(i.handlerFilePath, i);
}

for (const i of this.dtos) {
Expand Down
31 changes: 19 additions & 12 deletions src/Util/Feature/Meta/FeatureMeta.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { type JsonSerialize, LogicError } from '@hexancore/common';
import { LogicError, type JsonSerialize } from '@hexancore/common';
import { hash } from "node:crypto";
import type { FeatureHObjectMap, FeatureHObjectMeta } from './CommonFeatureMeta';
import { FeatureApplicationMeta } from './FeatureApplicationMeta';
import type { FeatureHObjectMeta } from './CommonFeatureMeta';
import { FeatureApplicationMeta, type FeatureApplicationCqrsHandlerMap } from './FeatureApplicationMeta';
import { FeatureDomainMeta } from './FeatureDomainMeta';
import { FeatureInfrastructureMeta } from './FeatureInfrastructureMeta';

export type FeatureMap = Map<string, FeatureMeta>;

export class FeatureMeta implements JsonSerialize {
private _hObjectMap!: Map<string, FeatureHObjectMeta>;
private _applicationCqrsHandlerMap!: FeatureApplicationCqrsHandlerMap;
public cacheKey!: string;

public constructor(
Expand Down Expand Up @@ -50,19 +51,25 @@ export class FeatureMeta implements JsonSerialize {
}

public get hObjectMap(): Map<string, FeatureHObjectMeta> {
if (!this._hObjectMap) {
this._hObjectMap = this.createHObjectMap();
}

this.initHObjectMaps();
return this._hObjectMap;
}

private createHObjectMap(): FeatureHObjectMap {
const map = new Map();
this.application.collectHObjects(map);
this.domain.collectHObjects(map);
public get applicationCqrsHandlerMap(): FeatureApplicationCqrsHandlerMap {
this.initHObjectMaps();
return this._applicationCqrsHandlerMap;
}

private initHObjectMaps(): void {
if (this._hObjectMap) {
return;
}

this._hObjectMap = new Map();
this._applicationCqrsHandlerMap = new Map();

return map;
this.application.collectHObjects(this._hObjectMap, this._applicationCqrsHandlerMap);
this.domain.collectHObjects(this._hObjectMap);
}

public toJSON(): any {
Expand Down
1 change: 1 addition & 0 deletions src/Util/Feature/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Meta";
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { HCommandHandler } from "@/Application/HCommandHandler";
import { OKA, ERRA, type HCommandAsyncResultType } from "@hexancore/common";
import { BookDto } from "../../Dto/BookDto";
import { BookCreateCommand } from "./BookCreateCommand";
import { CommandHandler } from "@nestjs/cqrs";

@CommandHandler(BookCreateCommand)
export class BookCreateCommandHandler extends HCommandHandler<BookCreateCommand> {
protected handle(command: BookCreateCommand): HCommandAsyncResultType<BookCreateCommand> {
if (command.title === "error") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { OKA, ERRA, type HQueryAsyncResultType } from "@hexancore/common";
import { QueryHandler } from "@nestjs/cqrs";
import { BookGetByIdQuery } from "./BookGetByIdQuery";
import { BookDto } from "../../Dto/BookDto";
import { HQueryHandler } from "@/Application/HQueryHandler";

@QueryHandler(BookGetByIdQuery)
export class BookGetByIdQueryHandler extends HQueryHandler<BookGetByIdQuery> {
protected handle(query: BookGetByIdQuery): HQueryAsyncResultType<BookGetByIdQuery> {
if (query.title === "error") {
Expand Down

0 comments on commit da4a94d

Please sign in to comment.