Skip to content

Commit

Permalink
feat(feature): enchant feature meta
Browse files Browse the repository at this point in the history
  • Loading branch information
Mararok committed Sep 12, 2024
1 parent a17327f commit 407b72b
Show file tree
Hide file tree
Showing 31 changed files with 963 additions and 364 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"jest": "node --disable-warning=ExperimentalWarning --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.ts --runInBand",
"test": "yarn run jest --runInBand",
"test:cc": "yarn run jest --clearCache",
"test:compiler": "yarn run build yarn && run test:cc && yarn run test",
"test:compiler": "yarn run build && yarn run test:cc && yarn run test",
"test:unit": "yarn run jest --runInBand --group=unit",
"test:watch": "yarn run jest --runInBand --watchAll",
"test:cov": "yarn run jest --coverage",
Expand Down Expand Up @@ -114,6 +114,7 @@
"@nestjs/swagger": "^7.1.8",
"@nestjs/testing": "^10.3.9",
"@types/jest": "29.0.*",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.14.2",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
Expand Down
53 changes: 53 additions & 0 deletions src/Util/Feature/FeatureApplicationDiscoverer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { type PathsOutput } from "fdir";
import path from "node:path";
import { FeatureDiscovererHelper } from "./FeatureDiscovererHelper";
import { FeatureApplicationCommandMeta, FeatureApplicationMeta, FeatureApplicationQueryMeta, FeatureApplicationServiceMeta, FeatureDtoMeta } from "./Meta";

export class FeatureApplicationDiscoverer {

public async discover(featureDir: string): Promise<FeatureApplicationMeta> {
const contextDirs: PathsOutput = await FeatureDiscovererHelper.crawlDirOnly(path.join(featureDir, "Application"));

const commands: FeatureApplicationCommandMeta[] = [];
const queries: FeatureApplicationQueryMeta[] = [];
const dtos: FeatureDtoMeta[] = [];
const services: FeatureApplicationServiceMeta[] = [];

for (const contextDir of contextDirs) {
const contextName = path.basename(contextDir);
commands.push(...(await this.discoverCommands(contextName, contextDir, featureDir)));
queries.push(...(await this.discoverQueries(contextName, contextDir, featureDir)));
services.push(...(await this.discoverServices(contextName, contextDir, featureDir)));
dtos.push(...(await this.discoverDtos(contextName, contextDir, featureDir)));
}

return new FeatureApplicationMeta(commands, queries, dtos, services);
}

private async discoverCommands(context: string, contextDir: string, featureDir: string): Promise<FeatureApplicationCommandMeta[]> {
return FeatureDiscovererHelper.discoverInDirOnlyDirs(
contextDir + 'Command',
featureDir,
(name) => new FeatureApplicationCommandMeta(name, context));
}

private async discoverQueries(context: string, contextDir: string, featureDir: string): Promise<FeatureApplicationQueryMeta[]> {
return FeatureDiscovererHelper.discoverInDirOnlyDirs(
contextDir + 'Query',
featureDir,
(name) => new FeatureApplicationQueryMeta(name, context));
}

private async discoverDtos(context: string, contextDir: string, featureDir: string): Promise<FeatureDtoMeta[]> {
return FeatureDiscovererHelper.discoverInDir(contextDir + 'Dto', featureDir,
(name, pathInFeature) => new FeatureDtoMeta(name, context, pathInFeature)
);
}

private async discoverServices(context: string, contextDir: string, featureDir: string): Promise<FeatureApplicationServiceMeta[]> {
return FeatureDiscovererHelper.discoverInDir(contextDir + 'Service', featureDir,
async (name, pathInFeature, filePath) =>
new FeatureApplicationServiceMeta(name, context, pathInFeature, await FeatureDiscovererHelper.isUsing(filePath, "@Injectable("))
);
}
}
99 changes: 99 additions & 0 deletions src/Util/Feature/FeatureDiscovererHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { fdir, type PathsOutput } from "fdir";
import { readFile } from 'node:fs/promises';
import { pathExists } from 'fs-extra';
import path from "node:path";

export class FeatureDiscovererHelper {

public static createDirClawler(): fdir<PathsOutput> {
return (new fdir()).withPathSeparator("/");
}

public static async crawlDirOnly(dir: string): Promise<PathsOutput> {
const crawler = this.createDirClawler()
.onlyDirs()
.withFullPaths()
.withMaxDepth(1);

const dirs = await crawler.crawl(dir).withPromise();
dirs.shift();
return dirs;
}

public static async isUsing(filePath: string, classOrInterfaceName: string): Promise<boolean> {
return (await readFile(filePath)).indexOf(classOrInterfaceName, 0, 'utf8') !== -1;
}

public static async discoverInDirOnlyDirs<T>(
dir: string,
featureDir: string,
mapper: (name: string, pathInFeature: string, filePath: string) => T | Promise<T>,
filter?: (filePath: string) => boolean | Promise<boolean>
): Promise<T[]> {
if (!(await pathExists(dir))) {
return [];
}

const dirIt = (new fdir()).withPathSeparator("/").onlyDirs().withMaxDepth(1).withFullPaths();
dirIt.filter((path) => !path.endsWith("index.ts"));
const filePaths = await dirIt.crawl(dir).withPromise();
filePaths.shift();
const metas: T[] = [];
for (const p of filePaths) {
if (filter) {
let result = filter(p);
result = result instanceof Promise ? await result : result;
if (!result) {
continue;
}
}

const name = path.basename(p);
const pathInFeature = this.relativePathInFeature(p, featureDir,true);
const mapped = mapper(name, pathInFeature, p);

metas.push(mapped instanceof Promise ? await mapped : mapped);
}

return metas;
}


public static async discoverInDir<T>(
dir: string,
featureDir: string,
mapper: (name: string, pathInFeature: string, filePath: string) => T | Promise<T>,
filter?: (filePath: string) => boolean | Promise<boolean>
): Promise<T[]> {
if (!(await pathExists(dir))) {
return [];
}

const dirIt = (new fdir()).withPathSeparator("/").withMaxDepth(0).withFullPaths();
dirIt.filter((path) => !path.endsWith("index.ts"));
const filePaths = await dirIt.crawl(dir).withPromise();
const metas: T[] = [];
for (const p of filePaths) {
if (filter) {
let result = filter(p);
result = result instanceof Promise ? await result : result;
if (!result) {
continue;
}
}

const name = path.basename(p, '.ts');
const pathInFeature = this.relativePathInFeature(p, featureDir, false);
const mapped = mapper(name, pathInFeature, p);

metas.push(mapped instanceof Promise ? await mapped : mapped);
}

return metas;
}

public static relativePathInFeature(path: string, featureDir: string, pathIsDir: boolean): string {
return path.substring(featureDir.length + 1, path.length - (pathIsDir ? 1 : 3));
}

}
65 changes: 65 additions & 0 deletions src/Util/Feature/FeatureDomainDiscoverer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { pathExists } from 'fs-extra';
import { type PathsOutput } from "fdir";
import path from "node:path";
import { FeatureAggregateRootMeta, FeatureDomainMeta, FeatureEntityMeta, FeatureValueObjectMeta } from "./Meta";
import { FeatureDiscovererHelper } from "./FeatureDiscovererHelper";

export class FeatureDomainDiscoverer {

public async discover(featureDir: string): Promise<FeatureDomainMeta> {
return new FeatureDomainMeta(
await this.discoverAggregateRoots(featureDir),
await this.discoverValueObjects('', path.join(featureDir, 'Domain'), featureDir),
);
}

private async discoverAggregateRoots(featureDir: string): Promise<FeatureAggregateRootMeta[]> {
const dirs: PathsOutput = await FeatureDiscovererHelper.crawlDirOnly(path.join(featureDir, "Domain"));

const roots: FeatureAggregateRootMeta[] = [];
for (const rootDir of dirs) {
const name = path.basename(rootDir, '.ts');
// skip not aggregates
if (name === 'Service' || name === 'Shared' || name === 'ValueObject' || name === 'Event') {
continue;
}

const entities = await this.discoverAggregateRootEntities(featureDir, rootDir, name);
const valueObjects = await this.discoverValueObjects(name, rootDir, featureDir);
roots.push(new FeatureAggregateRootMeta(name, entities, valueObjects));
}

return roots;
}

private async discoverAggregateRootEntities(featureDir: string, rootDir: string, aggregateRootName: string): Promise<FeatureEntityMeta[]> {
return FeatureDiscovererHelper.discoverInDir(rootDir, featureDir,
(name) => new FeatureEntityMeta(name, aggregateRootName),
(filePath) => filePath.endsWith('.ts') && FeatureDiscovererHelper.isUsing(filePath, "AbstractEntity")
);
}

private async discoverValueObjects(context: string, contextDir: string, featureDir: string): Promise<FeatureValueObjectMeta[]> {
const sharedValueObjectDir = path.join(contextDir, 'Shared/ValueObject');
let sharedValueObjects: FeatureValueObjectMeta[] | null = null;
if (await pathExists(sharedValueObjectDir)) {
sharedValueObjects = await FeatureDiscovererHelper.discoverInDir(sharedValueObjectDir, featureDir,
(name) => new FeatureValueObjectMeta(name, context, true)
);
}

const valueObjectDir = path.join(contextDir, 'ValueObject');
let valueObjects: FeatureValueObjectMeta[] | null = null;
if (await pathExists(valueObjectDir)) {
valueObjects = await FeatureDiscovererHelper.discoverInDir(valueObjectDir, featureDir,
(name) => new FeatureValueObjectMeta(name, '', false),
);
}

if (sharedValueObjects) {
return valueObjects ? [...sharedValueObjects, ...valueObjects] : sharedValueObjects;
}

return valueObjects ?? [];
}
}
17 changes: 17 additions & 0 deletions src/Util/Feature/FeatureInfrastructureDiscoverer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FeatureInfrastructureMeta, type FeatureClassMeta } from "./Meta";

export class FeatureInfrastructureDiscoverer {

public async discover(featureDir: string, featureName: string): Promise<FeatureInfrastructureMeta> {
return new FeatureInfrastructureMeta(
this.discoverInfrastructureModule(featureName)
);

}

private discoverInfrastructureModule(featureName: string): FeatureClassMeta {
const className = `${featureName}InfraModule`;
const pathInFeature = `Infrastructure/${className}`;
return { name: className, path: pathInFeature };
}
}
Loading

0 comments on commit 407b72b

Please sign in to comment.