service-ref.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { ClassDeclaration, Node, Scope, Type } from 'ts-morph';
  2. import { EntityRef } from './entity-ref';
  3. export interface ServiceFeatures {
  4. findOne: boolean;
  5. findAll: boolean;
  6. create: boolean;
  7. update: boolean;
  8. delete: boolean;
  9. }
  10. export class ServiceRef {
  11. readonly features: ServiceFeatures;
  12. readonly crudEntityRef?: EntityRef;
  13. get name(): string {
  14. return this.classDeclaration.getName() as string;
  15. }
  16. get nameCamelCase(): string {
  17. return this.name.charAt(0).toLowerCase() + this.name.slice(1);
  18. }
  19. get isCrudService(): boolean {
  20. return this.crudEntityRef !== undefined;
  21. }
  22. constructor(public readonly classDeclaration: ClassDeclaration) {
  23. this.features = {
  24. findOne: !!this.classDeclaration.getMethod('findOne'),
  25. findAll: !!this.classDeclaration.getMethod('findAll'),
  26. create: !!this.classDeclaration.getMethod('create'),
  27. update: !!this.classDeclaration.getMethod('update'),
  28. delete: !!this.classDeclaration.getMethod('delete'),
  29. };
  30. this.crudEntityRef = this.getEntityRef();
  31. }
  32. injectDependency(dependency: { scope?: Scope; name: string; type: string }) {
  33. for (const constructorDeclaration of this.classDeclaration.getConstructors()) {
  34. const existingParam = constructorDeclaration.getParameter(dependency.name);
  35. if (!existingParam) {
  36. constructorDeclaration.addParameter({
  37. name: dependency.name,
  38. type: dependency.type,
  39. hasQuestionToken: false,
  40. isReadonly: false,
  41. scope: dependency.scope ?? Scope.Private,
  42. });
  43. }
  44. }
  45. }
  46. private getEntityRef(): EntityRef | undefined {
  47. if (this.features.findOne) {
  48. const potentialCrudMethodNames = ['findOne', 'findAll', 'create', 'update', 'delete'];
  49. for (const methodName of potentialCrudMethodNames) {
  50. const findOneMethod = this.classDeclaration.getMethod(methodName);
  51. const returnType = findOneMethod?.getReturnType();
  52. if (returnType) {
  53. const unwrappedReturnType = this.unwrapReturnType(returnType);
  54. const typeDeclaration = unwrappedReturnType.getSymbolOrThrow().getDeclarations()[0];
  55. if (typeDeclaration && Node.isClassDeclaration(typeDeclaration)) {
  56. if (typeDeclaration.getExtends()?.getText() === 'VendureEntity') {
  57. return new EntityRef(typeDeclaration);
  58. }
  59. }
  60. }
  61. }
  62. }
  63. return;
  64. }
  65. private unwrapReturnType(returnType: Type): Type {
  66. if (returnType.isUnion()) {
  67. // get the non-null part of the union
  68. const nonNullType = returnType.getUnionTypes().find(t => !t.isNull() && !t.isUndefined());
  69. if (!nonNullType) {
  70. throw new Error('Could not find non-null type in union');
  71. }
  72. return this.unwrapReturnType(nonNullType);
  73. }
  74. const typeArguments = returnType.getTypeArguments();
  75. if (typeArguments.length) {
  76. return this.unwrapReturnType(typeArguments[0]);
  77. }
  78. const aliasTypeArguments = returnType.getAliasTypeArguments();
  79. if (aliasTypeArguments.length) {
  80. return this.unwrapReturnType(aliasTypeArguments[0]);
  81. }
  82. return returnType;
  83. }
  84. }