Skip to content

Instantly share code, notes, and snippets.

@wgd3
Created June 15, 2023 17:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wgd3/e86c6ebcf72fdb9f19582c4192a2f376 to your computer and use it in GitHub Desktop.
Save wgd3/e86c6ebcf72fdb9f19582c4192a2f376 to your computer and use it in GitHub Desktop.
Nx generator and template files used to create TypeORM entities, interfaces, services, and repositories.
import { <%=className%>Service } from '@myapp/server/core/application-services';
import { IRequestUserObject } from '@myapp/server/core/domain';
import { I<%=className%>, RoleEnum } from '@myapp/shared/domain';
import {
Body,
Controller,
Get,
Logger,
Param,
ParseUUIDPipe,
Post,
Query,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { ReqUser } from '../decorators/req-user.decorator';
import { PaginationQueryDto } from '../dtos/query-pagination.dto';
import { Create<%= className %>Dto } from '../dtos/create-<%= fileName %>.dto';
@Controller({ path: '<%=className%>', version: '1' })
@ApiTags('<%=className%>')
export class <%=className%>Controller {
private logger = new Logger(<%=className%>Controller.name);
constructor(private <%=propertyName%>Service: <%=className%>Service) {}
@Get('')
async getMany(
@ReqUser() user: IRequestUserObject,
@Query() searchOpts: unknown,
@Query() pagination: PaginationQueryDto<I<%=className%>>
): Promise<I<%=className%>[]> {
this.logger.log(`Returning <%=className%> for ${user.email}`);
return await this.<%=propertyName%>Service.getMany(
{ userId: user.userId },
searchOpts,
pagination
);
}
@Get(':id')
async getOne(
@ReqUser() user: IRequestUserObject,
@Param('id', new ParseUUIDPipe()) id: string
): Promise<I<%=className%>> {
return this.<%=propertyName%>Service.getOne(id, user.userId);
}
@Post('')
async createOne(
@ReqUser() user: IRequestUserObject,
@Body() data: Create<%= className %>Dto
): Promise<I<%=className%>> {
return this.<%=propertyName%>Service.createOne({
userId: user.userId,
...data,
});
}
}
import {IBaseModel} from './base.model';
import {IUserModel} from './user.model';
export interface I<%=className%>Relations {
user?: IUserModel;
}
export interface I<%= className %> extends IBaseModel {
userId: string;
}
export type ICreate<%= className %> = Omit<I<%=className%>, keyof IBaseModel>;
export type IUpdate<%= className %> = Partial<ICreate<%= className %>>;
import { Column, Entity } from 'typeorm';
import { I<%=className%> } from '@myapp/shared/domain';
import { BaseOrmEntity } from './base.orm-entity';
@Entity('<%= name %>')
export class <%= className %>OrmEntity extends BaseOrmEntity implements I<%=className%> {
@Column({
type: String
})
userId!: string;
}
import { I<%=className%>Repository } from '@myapp/server/core/domain-services';
import { I<%=className%> } from '@myapp/shared/domain';
export class <%=className%>OrmRepositoryAdapter implements I<%=className%>Repository {
constructor() {}
async getMany(): Promise<I<%=className%>[]> {
throw new Error('Method not implemented.');
}
async getOne(): Promise<I<%=className%>> {
throw new Error('Method not implemented.');
}
async createOne(): Promise<I<%=className%>> {
throw new Error('Method not implemented.');
}
async updateOne(): Promise<I<%=className%>> {
throw new Error('Method not implemented.');
}
async deleteOne(): Promise<null> {
throw new Error('Method not implemented.');
}
}
import {
I<%=className%>,
ICreate<%= className %>,
IUpdate<%= className %>
} from '@myapp/shared/domain';
export abstract class I<%=className%>Repository {
abstract getMany(): Promise<I<%=className%>[]>;
abstract getOne(id: string): Promise<I<%=className%>>;
abstract createOne(data: ICreate<%= className %>): Promise<I<%=className%>>;
abstract updateOne(id: string, data: IUpdate<%=className%>): Promise<I<%=className%>>;
abstract deleteOne(id: string): Promise<null>;
}
import { I<%= className %>Repository } from '@myapp/server/core/domain-services';
import { I<%= className %> } from '@myapp/shared/domain';
import { Injectable, Logger } from '@nestjs/common';
import { BaseCrudService } from './base-crud.service';
@Injectable()
export class <%= className %>Service extends BaseCrudService<I<%= className %>> {
constructor(private repo: I<%= className %>Repository) {
super(repo, new Logger(<%= className %>Service.name));
}
}
// import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
import { I<%= className %>, IBaseModel } from '@myapp/shared/domain';
// import { ApiProperty } from '@nestjs/swagger';
/**
* Entity doesn't need the base properties specified manually, and the userId will be inserted
* by the controller after reading the JWT.
*/
export class Create<%= className %>Dto implements Omit<I<%= className %>, keyof IBaseModel | 'userId'> {
/** EXAMPLE */
// @ApiProperty({
// type: String,
// nullable: false,
// required: true,
// example: 'Protein',
// })
// @IsString()
// @MinLength(2)
// @MaxLength(50)
// userId!: string;
}
import * as path from 'path';
import { SourceFile, SyntaxKind } from 'ts-morph';
import { formatFiles, generateFiles, names, Tree } from '@nx/devkit';
import { TypeormEntityGeneratorSchema } from './schema';
import { FileUpdates, updateSourceFiles } from './ts-morph-helper';
export async function typeormEntityGenerator(
tree: Tree,
options: TypeormEntityGeneratorSchema
) {
const nameVariants = names(options.entityName);
generateFiles(tree, path.join(__dirname, 'files'), '', { ...nameVariants });
const updates: FileUpdates = {
['libs/shared/domain/src/lib/models/index.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addExportDeclaration({
moduleSpecifier: `./${nameVariants.fileName}.model`,
});
},
['libs/server/core/domain-services/src/lib/repositories/index.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addExportDeclaration({
moduleSpecifier: `./${nameVariants.fileName}.repository`,
});
},
['libs/server/core/application-services/src/index.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addExportDeclaration({
moduleSpecifier: `./lib/${nameVariants.fileName}.service`,
});
},
['libs/server/infrastructure/src/lib/entities/index.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addExportDeclaration({
moduleSpecifier: `./${nameVariants.fileName}.orm-entity`,
});
},
['libs/server/infrastructure/src/lib/repositories/index.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addExportDeclaration({
moduleSpecifier: `./${nameVariants.fileName}.orm-repository-adapter`,
});
},
['libs/server/shell/src/lib/db.module.ts']: (sourceFile: SourceFile) => {
sourceFile.addImportDeclaration({
moduleSpecifier: `@myapp/server/infrastructure`,
namedImports: [`${nameVariants.className}OrmEntity`],
});
const entityArray = sourceFile
.getDescendantsOfKind(SyntaxKind.ArrayLiteralExpression)
.find(
(n) =>
n.getText().includes('OrmEntity') &&
!n.getText().includes('Subscriber')
)
.asKind(SyntaxKind.ArrayLiteralExpression);
entityArray.addElement(`${nameVariants.className}OrmEntity`);
},
['libs/server/shell/src/lib/server-shell.module.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addImportDeclaration({
moduleSpecifier: `@myapp/server/core/application-services`,
namedImports: [`${nameVariants.className}Service`],
});
sourceFile.addImportDeclaration({
moduleSpecifier: `@myapp/server/core/domain-services`,
namedImports: [`I${nameVariants.className}Repository`],
});
sourceFile.addImportDeclaration({
moduleSpecifier: `@myapp/server/infrastructure`,
namedImports: [`${nameVariants.className}OrmRepositoryAdapter`],
});
const serviceArray = sourceFile
.getDescendantsOfKind(SyntaxKind.ArrayLiteralExpression)
.find(
(n) =>
n.getText().includes('Service') &&
!n.getText().includes('RepositoryAdapter')
)
.asKind(SyntaxKind.ArrayLiteralExpression);
serviceArray.addElement(`${nameVariants.className}Service`);
const adapterArray = sourceFile
.getDescendantsOfKind(SyntaxKind.ArrayLiteralExpression)
.find((n) => n.getText().includes('RepositoryAdapter'))
.asKind(SyntaxKind.ArrayLiteralExpression);
adapterArray.addElement(
`{provide: I${nameVariants.className}Repository, useClass: ${nameVariants.className}OrmRepositoryAdapter}`
);
},
['libs/server/ui-rest/src/lib/server-ui-rest.module.ts']: (
sourceFile: SourceFile
) => {
sourceFile.addImportDeclaration({
moduleSpecifier: `./controllers/${nameVariants.fileName}.controller`,
namedImports: [`${nameVariants.className}Controller`],
});
const moduleClass = sourceFile.getClass((c) =>
c.getText().includes('@Module')
);
const moduleDecorator = moduleClass.getDecorator('Module');
const moduleArgs = moduleDecorator.getArguments()[0];
// console.log(`moduleArgs`, moduleArgs);
const controllersProp = moduleArgs
.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
.find((d) => d.getText().includes('controllers'));
// console.log(`controllerProp`, controllersProp);
const controllerArray = controllersProp.getFirstChildByKindOrThrow(
SyntaxKind.ArrayLiteralExpression
);
controllerArray.addElement(`${nameVariants.className}Controller`);
},
};
updateSourceFiles(tree, updates);
await formatFiles(tree);
}
export default typeormEntityGenerator;
export interface TypeormEntityGeneratorSchema {
entityName: string;
}
// copied from //https://ngserve.io/nx-how-to-write-a-generator/
import { Project, SourceFile } from 'ts-morph';
import { Tree } from '@nx/devkit';
export type UpdateFileContent = (sourceFile: SourceFile) => void;
export type FileUpdates = Record<string, UpdateFileContent>;
export type UpdateFileDelegate = (tree: Tree, project: Project) => void;
function updateFile(
path,
updateFileContent: UpdateFileContent
): UpdateFileDelegate {
return (tree: Tree, project: Project): void => {
const fileContents = tree.read(path).toString();
const sourceFile = project.createSourceFile(path, fileContents);
updateFileContent(sourceFile);
sourceFile.saveSync();
};
}
// need to know the source file being updated
// provide a deleagate the SourceFile to update
export const updateSourceFiles = (tree: Tree, fileUpdates: FileUpdates) => {
const project = new Project({
useInMemoryFileSystem: true,
});
const fs = project.getFileSystem();
Object.keys(fileUpdates).forEach((path) => {
const fileUpdate = updateFile(
path,
fileUpdates[path]
) as UpdateFileDelegate;
fileUpdate(tree, project);
tree.write(path, fs.readFileSync(path));
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment