Giter Club home page Giter Club logo

typeorm-typedi-extensions's People

Contributors

glen-84 avatar michallytek avatar nonameprovided avatar pleerock avatar xibre avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

typeorm-typedi-extensions's Issues

Constructor DI in entities

I wonder if it's possible to have a constructor depenency injection using TypeDI in entities. To clarify situation this is sample code, not real case ๐Ÿ˜‰ But I just need some logic in one of my entity which depend on 3rd party module:

@Service()
@Table()
export class DependentEntity {
    @Column()
    public name: string;

    private dependency: TheLib;
    public constructor(@Require("node-lib") dependency: TheLib) {
        this.dependency = dependency;
    }

    public doSomething() {
        return this.dependency.doSomething();
    }
}

It's so much easier to mock that the normal, require one:

import * as nodeLib from "node-lib";

@Table()
export class DependentEntity {
    @Column()
    public name: string;

    public doSomething() {
        return nodeLib.doSomething();
    }
}

When I can't use constructor DI, I can do something like this:

import * as nodeLib from "node-lib";

@Table()
export class DependentEntity {
    @Column()
    public name: string;

    private static dependency: TheLib = nodeLib;
    public static setDependency(dependency: TheLib) {
        this.dependency = dependency;
    }

    public doSomething() {
        return DependentEntity.dependency.doSomething();
    }
}

But then I had to remember to mock the dependency everytime in every test

beforeEach(() => {
    User.setDependency(new MockDependency())
});

because I don't have compilator error about missing constructor parameter. What will happen when there will be more classes and more dependencies?

I see in code that you just call constructor with no arguments:

if (this.target instanceof Function)
    return new (<any> this.target)();

But maybe you could first call Container.get(this.target)? Could it works? ๐Ÿ˜‹

question: repository type returns as empty object when used with `type-graphql` field resolver

@Service()
@Resolver(of => CampaignItem)
export class CampaignItemsResolver implements ResolverInterface<CampaignItem> {
  constructor(@injectRepository() private readonly campaignItemReposiory: CampaignItemRepository) {
  }
@FieldResolver({name: "publisherMetrics"})
  async publisherMetrics(@Root() data: CampaignItem):Promise<CampaignItemPublisherMetrics[]> {    
    return this.itemMetricsRepository.getMetricsByItemPublisher(data.id)
  }
}

When used with ResolverInterface & FieldResolver, the respository passed will be a function that empty object

Better way of mocking the injected repository

Function getRepository from src/decorators/InjectRepository.ts seems to be problematic.
I dealt with it like this:

// UserRepository.ts
import { EntityRepository, Repository } from 'typeorm';

@Service()
@EntityRepository(User)
export class UserRepository extends Repository<User> {  }
// UserResolver.ts
import { Service } from 'typedi';
import { InjectRepository } from 'typeorm-typedi-extensions';

@Service()
export class UserResolver {
    @InjectRepository()
    private readonly userRepository!: UserRepository;
}
// UserResolver.test.ts
import { ConnectionManager } from 'typeorm';

// mocked repository
class MockUserRepository {
    static findOne: jest.MockedFunction<typeof UserRepository.prototype.findOne>;
    static setupMocks() {
        this.findOne = jest.fn().mockResolvedValue(undefined);
    }
}
describe('UserResolver class', () => {
    beforeAll(() => {
        // This, as we know, is not enough for mocking @InjectRepository()
        // Container.set(UserRepository, MockUserRepository);

        // this will be used only once during the initial import so there is no need to put this in beforeEach
        Container.set(ConnectionManager, {
            has: (connectionName: string) => true,
            get: (connectionName: string) => ({
                getRepository: (entityType: any) => {
                    console.warn(`No mock repository found for ${entityType}`);
                },
                getMongoRepository: (entityType: any) => {
                    console.warn(`No mock repository found for ${entityType}`);
                },
                getTreeRepository: (entityType: any) => {
                    console.warn(`No mock repository found for ${entityType}`);
                },
                getCustomRepository: (repositoryType: any) => {
                    switch (repositoryType) {
                        case UserRepository:
                            return MockUserRepository; // here we mock our repository
                        default:
                            console.warn(`No mock repository found for ${repositoryType}`);
                    }
                },
            }),
        });
    });
    beforeEach(() => {
        MockUserRepository.setupMocks();
    });
    it('should pass', async () => {
        MockUserRepository.findOne.mockResolvedValue({});
        await <test>
        expect(MockUserRepository.findOne).toHaveBeenCalledWith({ email: '...' });
    });
});

if you inject repository as:

@InjectRepository(User)
private readonly userRepository!: Repository<User>;

then this should work:

Container.set(ConnectionManager, {
    has: (connectionName: string) => true,
    get: (connectionName: string) => ({
        getRepository: (entityType: any) => {
            switch (entityType) {
                case User:
                    return mockUserRepository;
                default:
                    console.warn(`No mock repository found for ${entityType}`);
            }
        },
    }),
});

Originally posted by @kajkal in #33 (comment)

A better way of mocking is clearly needed. It seems that the selected solution above does not fully solve the issue since you need to also mock the entire ConnectionManager. In other words, with the above solution only, any getConnection() calls would fail as it is not mocked (not to mention any other methods).

Better way means, you only mock what you need to mock.
Pretty much like

Container.set('UserRepository', mockUserRepository)

would be ideal.

Can you use custom Repository alone with typedi Container?

Does the custom repository only work in another repository? Can I use it alone with the Container from typeid?
The following code would throw something along the line Cannot read property 'findOne' of undefined.

  @Get("/test")
   @ContentType("application/json")
   async testing() {
       let repository = Container.get(UserRepository);
       return await repository.findByEmail('xxx');
   }
@Service()
@EntityRepository(User)
export class UserRepository extends Repository<User> {
    
    public findByEmail(email: string) {
        return this.findOne({ email });
    }
    
}

@Service()
export class PostService {

    // using constructor injection
    constructor(
        @OrmRepository()
        private readonly userRepository: UserRepository,
    ) {}

    public userExist(user: User): boolean {
        return this.userRepository.findByEmail(user.email) ? true : false;
    }

}

feature: use `TransactionManager` when inside Transactions

Is it possible to make multiple repository calls within a single transaction using dependency injection? If I'm reading the code correctly, this will always grab repositories off of a connection and therefore outside a transaction.

If it is possible, could that be added to the documentation?

Renaming decorators

I think that it would be better if we rename the @OrmRepository, @OrmManager and @OrmConnection decorators.

TypeDI has @Inject decorator so we should be consistent with this: @InjectRepository, @InjectManager and @InjectConnection ๐Ÿ˜‰ Other libs has the same convention.

@pleerock What do you think about this?

what am i doing wrong here?

for "fieldSetRepo" i get undefined help me with resolve it

import 'reflect-metadata';
import { Request } from 'express';
import Container, { Service, Inject } from 'typedi';
import { Repository, getRepository } from 'typeorm';
import { IResponse } from '@interfaces';
import { success } from '@utils/common.util';
import { FieldSettings } from '@models/field-settings.model';
import { InjectRepository } from 'typeorm-typedi-extensions';

@service()
class FieldSettingsController {

@InjectRepository(FieldSettings)
private fieldSetRepo: Repository<FieldSettings>;


private constructor() {
    // Private constructor to enforce singleton pattern
}

static getInstance(): FieldSettingsController {
    if (!FieldSettingsController.instance) {
        FieldSettingsController.instance = new FieldSettingsController();
    }
    return FieldSettingsController.instance;
}

private static instance: FieldSettingsController;

createFields = async (req: Request, res: IResponse): Promise<void> => {
    try {
        const fields = new FieldSettings({
            name: req.body.name
        });

        if (!this.fieldSetRepo) {
            throw new Error('Repository not initialized');
        }

        await this.fieldSetRepo.save(fields);

        res.status(200).json(success('', {}, res.statusCode));
    } catch (err) {
        console.error('Error:', err.message);
        res.status(400).json(success('', {}, res.statusCode));
    }
};

getAllFields =  async (req: Request, res: IResponse): Promise<void> => {
            try {
                const fields = await this.fieldSetRepo.find();
    
                res.status(200).json(success('', {fields}, res.statusCode));
            } catch (err) {
                console.log('err', err);
                res.status(400).json(success('', {}, res.statusCode));
            }
        }
// ... other methods ...

}

const fieldSettingsController = FieldSettingsController.getInstance();
export { fieldSettingsController as FieldSettingsController };

question: how to mock `InjectRepository`?

I couldn't find any way to mock repositories injected by InjectRepository.
Apparently Container.set is not the way to go.

I ended up mocking the whole library for now

jest.mock('typeorm-typedi-extensions', () => ({
  InjectRepository: () => () => {},
}));

...

service = Container.get(MyService);
service.repository = {...}

TypeError: typedi_1.Container.registerPropertyHandler is not a function

Hi there,
I've got this following error:

C:\Applications\Node\exotica\server>node app.js
C:\Applications\Node\exotica\server\node_modules\typeorm-typedi-extensions\decorators\OrmCustomRepository.js:23
typedi_1.Container.registerPropertyHandler({ target: target /* todo: looks like typedi wrong type here */, key: propertyName, getValue: ge
tValue });
^

TypeError: typedi_1.Container.registerPropertyHandler is not a function
at C:\Applications\Node\exotica\server\node_modules\typeorm-typedi-extensions\decorators\OrmCustomRepository.js:23:32
at DecorateProperty (C:\Applications\Node\exotica\server\node_modules\reflect-metadata\Reflect.js:530:29)
at Object.decorate (C:\Applications\Node\exotica\server\node_modules\reflect-metadata\Reflect.js:100:20)
at __decorate (C:\Applications\Node\exotica\server\dist\repository\TCandidatStatsRepository.js:4:92)
at Object. (C:\Applications\Node\exotica\server\dist\repository\TCandidatStatsRepository.js:47:1)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)

David

Update it to use latest TypeDI (0.5.0)

Hello!

Looks like this module depends on functionality removed/renamed from/in TypeDI 0.5.0, specifically: Container.registerParamHandler and Container.registerPropertyHandler.

Could you please update it?

--

Looks like you've renamed some methods and are now using single method instead of these two. Maybe we should add those methods to TypeDI for backward compatibility and mark them as @deprecated? Or just update all depending modules?

feature: support request scoped decorators (use container instance provided by TypeDI)

Problem:
Cannot use request scoped container instance

Decorators are currently using global container instead of passed container instance to value in registerHandler
https://github.com/typeorm/typeorm-typedi-extensions/blob/5016faf3d99dfbfcbcad2395f4bce2628d1f31f1/src/decorators/InjectConnection.ts#L9

Why:

Allows more use cases where request scope(TypeDI) is required

Solution:

  Container.registerHandler({
    object,
    index,
    propertyName,
    value: containerInstance => {
      const connectionManager = containerInstance.get(ConnectionManager);
      return connectionManager.get(connectionName);
    },
  });

Container Set

I'm setting that container set is not exposed - is there a reason ? Or is there a different approach ? I have a few edge case scenarios where I need to use set.

Allow @InjectRepository with a function

I have some circular depended entities (User and Token) and when I injecting Repository<User> and Repository<Token> in some class, User is undefined on the moment of the class construction.

class SomeController {
  @InjectRepository(Token)
  private tokenRepo: Repository<Token>

  @InjectRepository(User) // User is undefined, so error is occured
  private userRepo: Repository<User>
}

Error message: Missing "entityType" parameter of "@InjectRepository" decorator for a ...

If you allow to provide functions that returns class type, it will solve this problem:

  @InjectRepository(of => Token) // like in TypeDI: @Inject(type => SomeService)
  private tokenRepo: Repository<Token>

  @InjectRepository(of => User)
  private userRepo: Repository<User>

question: decorator parameters vs constructor ?

I don't understand the difference between use decorator parameters or decorator in constructor.

I mean diffรฉrence beetween that:

@Service()
export class PostRepository {
   constructor(
       @InjectRepository(Post)
       private repository: Repository<Post>
   ) {} 
}

And that

@Service()
export class PostRepository {
      @InjectRepository(Post)
       private repository: Repository<Post>
}

When should I use the constructor, is there a solution that should be prioritized?

Question: Scoped Containers and "default" connection

Trying to follow the below guideline from #39:

import { createConnection, useContainer } from 'typeorm';
import { Container } from 'typedi';

/** Tell TypeORM to use the TypeDI container to resolve it's dependencies. */
useContainer(Container);

/** Create a connection and start using TypeORM. */
createConnection({
  /* <connection options> */
}).catch(error => {
  console.error(`Couldn't connect to the database!`);
  console.error(error);
});

Originally posted by @NoNameProvided in #39 (comment)

and I get the new serviceNotFound: MaybeConstructable error. So I try using the Container exported from typeorm-typedi-extensions, and I get the "cannot get connection default" error.

Any guidance here on how to properly use scoped containers with TypeORM & TypeDI?

injected repository is always undefined

Hi
I'm trying to inject some repository into a routing-controllers controller like this:

import { Param, Body, Get, Post, Put, Delete, JsonController } from "routing-controllers";
import { InjectRepository } from "typeorm-typedi-extensions";
import { Repository } from "typeorm";
import { User } from "../entities/User";
import { Service } from "typedi";

@JsonController('/users')
@Service()
export class UserController {

    @InjectRepository(User)
    private repository: Repository<User>;

    @Get("/")
    getAll() {
        return this.repository.find();
    }
}

but this.repository is always undefined!

this is my app entry main.ts file:

import "reflect-metadata";
import { createConnection, useContainer} from 'typeorm';
import { createExpressServer } from "routing-controllers";
import { UserController } from "./src/controllers/UserController";

import { Container } from 'typedi';

useContainer(Container);
createConnection().then(async connection => {
}).catch(error => console.log(error));

const app = createExpressServer({
    controllers: [UserController] // we specify controllers we want to use
 });
 app.listen(3000);

all entities are created and express is working.

documentation: I need more "meat" about - for what this module need?

Why haven't I use clear typeorm connections?

import {data.Model as DataModel} from 'Core';
class Account extends DataModel {
    @Column({readonly: false, select: true, type: 'text', nullable: false, primary: false, unique: true, array: false})
    name;
}
let user = new Account();
user.find({name: 'pupi', limit: 1})

console.info(user.name)

That's my simple example. Maybe typedi give me more usabilities?

what am i doing wrong here? i have used useContainer in config class.

for "fieldSetRepo" i get undefined help me with resolve it

import 'reflect-metadata';
import { Request } from 'express';
import Container, { Service, Inject } from 'typedi';
import { Repository, getRepository } from 'typeorm';
import { IResponse } from '@interfaces';
import { success } from '@utils/common.util';
import { FieldSettings } from '@models/field-settings.model';
import { InjectRepository } from 'typeorm-typedi-extensions';

@service()
class FieldSettingsController {

@InjectRepository(FieldSettings)
private fieldSetRepo: Repository<FieldSettings>;


private constructor() {
    // Private constructor to enforce singleton pattern
}

static getInstance(): FieldSettingsController {
    if (!FieldSettingsController.instance) {
        FieldSettingsController.instance = new FieldSettingsController();
    }
    return FieldSettingsController.instance;
}

private static instance: FieldSettingsController;

createFields = async (req: Request, res: IResponse): Promise<void> => {
    try {
        const fields = new FieldSettings({
            name: req.body.name
        });

        if (!this.fieldSetRepo) {
            throw new Error('Repository not initialized');
        }

        await this.fieldSetRepo.save(fields);

        res.status(200).json(success('', {}, res.statusCode));
    } catch (err) {
        console.error('Error:', err.message);
        res.status(400).json(success('', {}, res.statusCode));
    }
};

getAllFields =  async (req: Request, res: IResponse): Promise<void> => {
            try {
                const fields = await this.fieldSetRepo.find();
    
                res.status(200).json(success('', {fields}, res.statusCode));
            } catch (err) {
                console.log('err', err);
                res.status(400).json(success('', {}, res.statusCode));
            }
        }
// ... other methods ...

}

const fieldSettingsController = FieldSettingsController.getInstance();
export { fieldSettingsController as FieldSettingsController };

Question: Error inject repository after transpile

When i start my application in develop mode, all works fine, but when transpile files.
I use ts-node-dev to start app.

But when i transpile files with babel and start application, i get the error:
UnhandledPromiseRejectionWarning: ParamTypeMissingError: Cannot get reflected type for a "repository" method's [object Object]1. parameter of OrderService class. Make sure you have turned on an "emitDecoratorMetadata": true, option in tsconfig.json. and that you have imported "reflect-metadata" on top of the main entry file in your application.And make sure that you have annotated the property type correctly with: Repository, MongoRepository, TreeRepository or custom repository class type.

emitDecoratorMetadata is true;
reflect-metadata is imported;

File that start application
server.ts

import 'reflect-metadata';
import { Container } from 'typedi';

import connection from '@config/db';
import { server } from '@config/globals';

import logger from '@middlewares/logger';
import { RabbitClient } from '@utils/rabbitmq/RabbitClient';

connection.then(async () => {
  Container.set(RabbitClient, new RabbitClient());
  const app = (await import('./app')).default;
  app.listen(server.port, () => {
    logger.info(`Server running`, server);
  });
});

OrderServive.ts

import { Inject, Service } from 'typedi';
import { InjectRepository } from 'typeorm-typedi-extensions';

import { dbConnections, rabbit } from './../../config/globals';
import { Order } from './order.entity';
import { OrderRepository } from './OrderRepository';
import { OrderStatus, TransactionStatus } from './types';

@Service()
export class OrderService {
  @InjectRepository(dbConnections.mongo.name)
  private repository!: OrderRepository;

  @Inject()
  private publisher!: RabbitPublisher;

  ....
}

OrderRepository.ts

import { EntityRepository, MongoRepository } from 'typeorm';

import { Order } from './order.entity';

@EntityRepository(Order)
export class OrderRepository extends MongoRepository<Order> {}

[Question] Can inject connection, but which one?

According to TypeORM docs on connections, we can use multiple connections, you can inject the connection with the TypeDI injector, however which connection get's injected when multiple connections were initialized? the one used to request the repository?

question: integration testing with typeorm returning `undefined`

Issue

OrmRepository injection works fine when running the server normally (can do a successful getAll() request). However, when I try to implement integration testing I get a RepositoryNotFoundError.

Packages involved

  • Typedi
  • Typeorm
  • Typedi-typeorm-extensions
  • mocha
  • chai

Code

bootstrap.ts
import 'reflect-metadata';
import { useContainer as routingUseContainer, useExpressServer } from 'routing-controllers';
import { useContainer as ormUseContainer, getConnection } from 'typeorm';
import { Container } from 'typedi';
import { getDbConnection } from './db';

routingUseContainer(Container);
ormUseContainer(Container);

const controllerPath = path.join(config.appRootDir, '/dist/api/controllers/*.js');
const middlewarePath = path.join(config.appRootDir, '/dist/api/middleware/*.js');

async function bootstrap(): Promise<express.Express> {
  const app = express();

  useExpressServer(app, {
    routePrefix: '/api',
    controllers: [controllerPath],
    middlewares: [middlewarePath],
  });

  config.dbConnection = await getDbConnection();

  return app;
}
movie.service.ts
import { Service } from 'typedi';
import { Repository } from 'typeorm';
import { OrmRepository } from 'typeorm-typedi-extensions';
import Movie from '../models/movie.model';

export interface IMovie {
  title: string;
  year: number;
}

@Service()
export default class MovieService {

  constructor(
    @OrmRepository(Movie)
    private movieRepository: Repository<Movie>,
  ) { }

  public async getAll(): Promise<IMovie[]> {
    return await this.movieRepository.find();
  }
}
movieService.test.ts
import * as chai from 'chai';
import { Express } from 'express';
import 'mocha';
import { Container } from 'typedi';
import Movie from '../../api/models/movie.model';
import MovieService from '../../api/services/movie.service';
import bootstrap from '../../config/bootstrap';
import config from '../../config/config';

const expect = chai.expect;
let app: Express;

describe('MovieService', () => {
  before(async () => {
    app = await bootstrap();
  });

  after(async () => {
    return await config.dbConnection.close();
  });

  it('should create a new movie in the database', async () => {
    const movie = await new Movie();
    movie.title = 'test';
    movie.year = 2018;
    const movieService = await Container.get<MovieService>(MovieService);
    console.log(movieService);
    console.log('here');
  });
});

The test exists on const movieService = await Container.get<MovieService>(MovieService); with a RepositoryNotFoundError. The console.log lines after this are not executed.

fix: Using Typeorm DataSource and Typegraphql: Cannot get Connection with name \"default\" from the ConnectionManager.

Description

Using Typeorm's new way of connecting to a data source with new Typeorm.DataSource({...}) , we cannot access the global typeorm connection through the typedi container. I'm using Typegraphql and when I run a query I get the response error:

Cannot get Connection with name \"default\" from the ConnectionManager. Make sure you have created the connection and called \"useContainer(Container)\" in your application before establishing a connection and importing any entity into TypeORM.

It only works if we create the connection using the now deprecated Typeorm.createConnection.

As a side note, Typeorm has deprecated all container-related features in 0.3.0. How will this affect the future of this package and Typedi as a whole?

Minimal code-snippet showcasing the problem

import * as TypeGraphQL from 'type-graphql';
import { Container } from 'typeorm-typedi-extensions';

TypeORM.useContainer(Container);

export const AppDataSource = new DataSource(typeormOptions); 
await AppDataSource.initialize(); // this will establish the connection, but Typegraphql can't find the connection

// await TypeORM.createConnection(typeormOptions); // this still works however

const schema = await TypeGraphQL.buildSchema({
   ...
   container: Container,
});

Expected behavior

I expected to my query to be able to find the injected Repository and Connection and run the query.

Actual behavior

Typegraphql sends back the response:
Cannot get Connection with name \"default\" from the ConnectionManager. Make sure you have created the connection and called \"useContainer(Container)\" in your application before establishing a connection and importing any entity into TypeORM.

question: how to inject repository with ciruclar services?

Service A injects to Service B and Service B injects to Service A.

@Service()
@Resolver()
@EntityRepository(ProjectEntity)
export class ProjectService extends DatabaseService<ProjectEntity> {
	@InjectRepository("custom-pTransaction-service")
	private readonly pTransactionService: ProjectTransactionService;
}

@Service()
@Resolver()
@EntityRepository(ProjectTransactionEntity)
export class ProjectTransactionService extends DatabaseService<ProjectTransactionEntity> {

	@InjectRepository("custom-project-service")
	private projectService: ProjectService;
}
const project: any = await this.projectService.readOneProject(pTransactionInput.projectId);

Where readOneProject also uses "ProjectTransactionService`.

This doesn't work.

Versions:

  • "typedi": "^0.8.0",
  • "typeorm": "^0.2.12",
  • "typeorm-typedi-extensions": "^0.2.3"

spelling

Dependancy sounds fun but should really be dependency :)

question: Error: Cannot get connection "default" from the connection manager.

Error: Cannot get connection "default" from the connection manager. Make sure you have created such connection. Also make sure you have called useContainer(Container) in your application before you established a connection and importing any entity.
    at Object.value (/src/decorators/InjectConnection.ts:12:23)
    at /app/src/ContainerInstance.ts:355:37
    at Array.map (<anonymous>)
    at ContainerInstance.initializeParams (/app/src/ContainerInstance.ts:352:27)
    at ContainerInstance.getServiceValue (/app/src/ContainerInstance.ts:305:47)
    at ContainerInstance.get (/app/src/ContainerInstance.ts:119:21)
    at Object.value (/app/src/decorators/Inject.ts:48:42)
    at /app/src/ContainerInstance.ts:355:37
    at Array.map (<anonymous>)
    at ContainerInstance.initializeParams (/app/src/ContainerInstance.ts:352:27)
[nodemon] app crashed - waiting for file changes before starting...

My index.ts

import 'reflect-metadata';

import { Container } from 'typedi';
import { useContainer } from 'typeorm';
import Application from 'src/Application';

useContainer(Container);

const application = Container.get(Application);

application.start();

"Cannot get reflected type for a "undefined" method's 1. parameter"

I'm trying to get my jest unit test to work, but it keeps on failing with this error message.
Here's the error message in more detail:

Cannot get reflected type for a "undefined" method's 1. parameter of Function class. Make sure you have turned on an "emitDecoratorMetadata": true, option in tsconfig.json. and that you have imported "reflect-metadata" on top of the main entry file in your application.And make sure that you have annotated the property type correctly with: Repository, MongoRepository, TreeRepository or custom repository class type.

       7 | @Service()
       8 | export class UserService {
    >  9 |   constructor(@InjectRepository() public readonly repository: UserRepository) {}
         |                                    ^
      10 | 
      11 |   public register = async (user: User) => this.repository.save(user)
      12 | 

      at new ParamTypeMissingError (server/node_modules/typeorm-typedi-extensions/errors/ParamTypeMissingError.js:16:28)
      at server/node_modules/typeorm-typedi-extensions/decorators/InjectRepository.js:49:23
      at server/src/services/user.service.ts:9:36
      at DecorateConstructor (server/node_modules/reflect-metadata/Reflect.js:541:33)
      at Object.decorate (server/node_modules/reflect-metadata/Reflect.js:130:24)
      at Object.<anonymous>.__decorate (server/src/services/user.service.ts:4:92)
      at server/src/services/user.service.ts:98:19
      at Object.<anonymous> (server/src/services/user.service.ts:103:2)
      at Object.<anonymous> (server/src/services/user.spec.ts:40:22)

Here's my test code that is failing:

beforeAll(async () => {
      user.username = username
      await user.setPassword(validPassword)

      MockRepository.mockImplementation(() => ({
        findOne: jest.fn(({ username }) => {
          if (username === '[email protected]') {
            return user
          } else {
            return undefined
          }
        }),
      }))
      repository = new MockRepository()
      service = new UserService(repository) // this is where the fail happens
    })

My repository:

@Service()
@EntityRepository(User)
export class UserRepository extends Repository<User> {}

My service class:

@Service('user.service')
export class UserService {
  constructor(@InjectRepository() public readonly repository: UserRepository) {}
}

I do have import reflect-metadata, emitMetdata, etc. all setup so I'm confused. I would have thought that passing in a value to the constructor would bypass InjectRepository().
Of note, removing the @InjectRepository annotation removes the jest error.
Also, with the InjectRepository annotation, and when I start the server, it works just fine.

So the fact that there is no container is the problem, I guess, but I shouldn't have to set one up for a unit test?

I was using an older version of typeorm-typedi-extensions and it worked fine, but when I downgraded from the latest, it did not work either. So, not sure what changed...

Extending repository class

I wonder how to easily extend Repository<T> class. I tried normal extend:

@Service()
export class UserRepository extends Repository<User> { 
    public findByEmail(email: string): Promise<User | undefined> {
        return this.findOne({ email: email });
    }
}

But it throws error about missing metadata and connection stuffs - TypeDI can't figure out by itself that it should call super constructor and inject some stuffs there to make it work.

So I try to create instantion of super class by calling a super constructor:

@Service()
export class UserRepository extends Repository<User> {

    public constructor(@OrmConnection() connection: Connection) {
        super(connection, connection.getMetadata(User));
    }

    public findByEmail(email: string): Promise<User | undefined> {
        return this.findOne({ email: email });
    }
}

And it works great but it's a boilerplate which I had to copy-paste in all other repository for my entities.

And also this dependency injection isn't solving testability problem - I don't know how to mock OrmConnection and test my findByEmail method by mocking findOne method.

How to inject repository when using separate entity schema and not decorators?

If I have an separate entity schema like such

export const ProjectEntity = new EntitySchema<Project>({
    name: 'project',
    columns: {
        id: {
            type: Number,
            primary: true,
            generated: true
        },
        userId: {
            type: Number,
        },
        name: {
            type: String,
            nullable: false
        }
    }
})

And a corresponding service

@Service(Services.PROJECT)
export class ProjectService {

    constructor (
        @InjectRepository(Project) private repository: ProjectRepository
    ) {}

The the following error will be thrown when trying to instantiate the service

No metadata for \"Project\" was found.

EntityFromParam decorator error when param value is uuid

Summary

This is a bit of a weird one. In short when using a uuid in my request param I am not able to use the EntityFromParam decorator in my controller action for fetching entities.

After digging into this a little bit it appears that this is a result of the implementation in typestack/routing-controllers here: https://github.com/typestack/routing-controllers/blob/master/src/ActionParameterHandler.ts#L136-L139

Since the the param type is an Entity constructor there is no way to prevent typestack/routing-controllers from passing the uuid to JSON.parse resulting in an error.

There was a related issue here: typestack/routing-controllers#51, but it was closed since they were not using a constructor as the param type.

Steps to Reproduce

  1. Create typeorm entity using @PrimaryGeneratedColumn("uuid") for id
  2. Create controller action using get(@EntityFromParam('id') entity: Entity) {}
  3. Attempt to fetch entity http://localhost:3000/entity/:id where :id is the uuid of the entity

Error Message

Error
    at ParameterParseJsonError.HttpError [as constructor] (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/http-error/HttpError.ts:19:22)
    at ParameterParseJsonError.BadRequestError [as constructor] (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/http-error/BadRequestError.ts:10:9)
    at new ParameterParseJsonError (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/error/ParameterParseJsonError.ts:10:9)
    at ActionParameterHandler.parseValue (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/ActionParameterHandler.ts:153:23)
    at ActionParameterHandler.normalizeParamValue (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/ActionParameterHandler.ts:137:34)
    at ActionParameterHandler.handle (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/ActionParameterHandler.ts:44:28)
    at /Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/RoutingControllers.ts:116:49
    at Array.map (<anonymous>)
    at RoutingControllers.executeAction (/Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/RoutingControllers.ts:116:14)
    at /Users/edwardpfremmer/Projects/pfrembot/react-relationships/src/RoutingControllers.ts:83:33

There are a couple of workarounds that I have tested, but they feel less elegant:

  • Wrap uuid in quotes in URL: Surprisingly enough this actually worked causing JSON.parse to parse it as a serialized string
  • Use regular @param instead: Get the route param and handle fetching the typeorm entity by hand
  • Implement a custom decorator: Since custom decorators get set as type "custom-converter" preventing parsing the uuid

Sample Decorator

import { createParamDecorator } from "routing-controllers";
import { Action } from "routing-controllers/Action";
import { entityTransform } from "typeorm-routing-controllers-extensions/util/Utils";

export function MyCustomDecorator(paramName: string, options?: { required?: boolean }) {
  return createParamDecorator({
    required: Boolean(options && options.required),

    // note: do not change to fat-arrow function (this bound as ParamMetadata)
    value: function (action: Action) {
      const value = action.request.params[paramName];

      return entityTransform(value, this.targetType, false);
    }
  });
}

I apologize that I am creating this bug on here instead of typestack/routing-controllers, but it felt like most of the context of the problem was centered around @EntityFromParam.

Also I absolutely love this project! Keep up the amazing work!!!

How to inject a repository into another one ?

My repository candidatRepository is undefined.
How can i inject it into another repository ?

import {Service} from "typedi";
import { Repository, EntityRepository } from "typeorm";
import {OrmCustomRepository} from "../../node_modules/typeorm-typedi-extensions/decorators/OrmCustomRepository";
import {TCandidatRepository} from "../repository/TCandidatRepository";
import {TCandidatStats} from "../entity/TCandidatStats";

// create custom Repository class
@Service()
@EntityRepository(TCandidatStats)
export class TCandidatStatsRepository extends Repository<TCandidatStats>
{
    //property injection
    @OrmCustomRepository(TCandidatRepository)
    private candidatRepository: TCandidatRepository

    updateDateConnexion(candidat_id: number) {
            let self = this;
            return  this.findOneById(candidat_id)
                    .then(function(candidatStats) {
                        if(!candidatStats) {
                            return self.candidatRepository.findOneById(candidat_id) 
// =>  self.candidatRepository IS UNDEFINED
                            .then(function(candidat) {
                                console.log("SECOND");
                                candidatStats = new TCandidatStats();
                                candidatStats.setCandidat(candidat);
                                candidatStats.setDateInscription(new Date());
                                return candidatStats;
                            });
                        }
                        return candidatStats;
                    })
                    .then(function(candidatStats) {
                        candidatStats.setDateDerniereConnection(new Date());
                        return self.persist(candidatStats);
                    });
            
    }	
}

Getting error "Unexpected token =" when injecting

I am having issues with injecting TypeORM connection, getting the unhelpful error message "SyntaxError: Unexpected token =".

Here is a typescript file to reproduce the error. I am running this using the latest ts-node, 2.1.0.

import 'reflect-metadata';
import { Container, Inject } from 'typedi';
import { OrmConnection } from 'typeorm-typedi-extensions';
import { useContainer, createConnection, Connection, ConnectionOptions } from 'typeorm';

class App {
	@OrmConnection()
	connection: Connection;

	go() {
		console.log('connection', this.connection);
	}
}

useContainer(Container);

const options: ConnectionOptions = {
	driver: {
		type: 'mysql',
		host: 'localhost',
		port: 3306,
		username: 'develop',
		password: '...',
		database: 'mydb'
	},
	entities: []
};
createConnection(options).then(() => {
	const app: App = Container.get(App);
	app.go();
});

This prints:

SyntaxError: Unexpected token =
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object.<anonymous> (/private/var/www/project/src/index.ts:1:1)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
error Command failed with exit code 1.

Am I doing something wrong?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.