typestack / typeorm-typedi-extensions Goto Github PK
View Code? Open in Web Editor NEWDependency injection and service container integration with TypeORM using TypeDI library.
License: MIT License
Dependency injection and service container integration with TypeORM using TypeDI library.
License: MIT License
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? ๐
@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
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.
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;
}
}
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?
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?
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 };
error sorry
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 = {...}
Hi there,
I'm trying to use these method:
https://github.com/typeorm/typeorm-typedi-extensions?#ormcustomrepository
But OrmCustomRepository does not exist :
import { OrmCustomRepository } from "typeorm-typedi-extensions";
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
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?
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);
},
});
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.
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>
TypeORM version: 0.2.18
Extension version: 0.2.3
await createConnection();
I'm using the default .env
connection config.
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?
order: { createdAt: 'DESC' },
relations: ['r1', 'r2'],
skip: 1,
take: 25,
error ==> duplicated columns
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?
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.
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?
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 };
Please update typedi
peer dependency to ^0.8.0
(currently "typedi": "^0.7.2"
).
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> {}
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?
While running build file created by Babel. I'm getting this error. I have turned on an "emitDecoratorMetadata": true, option in tsconfig.json and imported "reflect-metadata" on top of the main entry file.
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
.
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;
}
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();
}
}
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.
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,
});
I expected to my query to be able to find the injected Repository and Connection and run the query.
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.
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:
Dependancy sounds fun but should really be dependency :)
I get this warning after npm install.
"routing-controllers": "^0.7.0-alpha.11",
"typedi": "^0.5.2",
"typeorm": "0.1.0-alpha.19",
"typeorm-typedi-extensions": "0.0.12",
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();
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...
There is a mistake:
https://github.com/typeorm/typeorm-typedi-extensions/blob/b6d540e2084c832a66573b23374384d367f725f5/src/errors/entity-type-missing.error.ts#L6
The addition operation will be executed first and the result will never be undefined. For the same reason Missing "entityType" parameter of "@InjectRepository" decorator
will never be printed.
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.
Subj.
[email protected] installed but npm did not trust me
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.
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.
@PrimaryGeneratedColumn("uuid")
for idget(@EntityFromParam('id') entity: Entity) {}
http://localhost:3000/entity/:id
where :id is the uuid of the entityError
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:
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!!!
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);
});
}
}
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.