When registering entities through MirkoORM.forFeature()
method it automatically registers the repositories if I have the entity properly set up.
This is an example entity I have
@Entity({
tableName: 'refresh_token',
customRepository: () => RefreshTokenRepository
})
export default class RefreshToken extends BaseEntity<RefreshToken, 'id'> {
[EntityRepositoryType]?: RefreshTokenRepository
@PrimaryKey({
type: 'uuid',
nullable: false,
autoincrement: false,
unique: true,
primary: true
})
id!: string
}
In my application service I use the following:
@Injectable()
export default class AuthenticationService {
private logger = new Logger(AuthenticationService.name)
constructor(
private readonly orm: MikroORM,
private readonly jwtService: JwtService,
private readonly passwordVerifier: PasswordVerifier,
private readonly userRepository: UserRepository,
private readonly refreshTokenRepository: RefreshTokenRepository
) {}
@CreateRequestContext()
private async passwordGrantType(
payload: IssueTokenPayload
): Promise<Either<AccessDeniedError | UnexpectedError, TokenResponse>> {
const em = this.orm.em
}
Using this.orm.em
will give me a new instance indeed but then there's the question: if I inject the private readonly refreshTokenRepository: RefreshTokenRepository
will this give me a fresh instance of the EM inside the repository?
Is there a difference using const em = this.orm.em
or const em = this.refreshTokenRepository.getEntityManager()
?
If I need to inject orm: MirkoORM
to get a fresh instance then what is the point of injecting this repository?
EDIT 1
I have added this to my main.ts
;(async function () {
const app = await NestFactory.create(AppModule, { bufferLogs: true })
const orm = app.get(MikroORM)
const loggerService = app.get(AppBindings.Logger)
app.use((req, res, next) => {
RequestContext.create(orm.em, next)
})
Using this with my repository does not work. I still get populated collections used in previous methods.
Example: when user is accessing a protected endpoint profile
I will check if user has required roles and scopes. (during each request I will attach scopes to the role = this will be refactored later) For this I will query the roleRepository inside my JWT strategy and populate permissions for it.
async permissionsOfRole(roleId: string): Promise<Permission[]> {
const role = await this.findOne({ id: roleId }, { populate: ['permissions'] })
if (!role) return null
return role.permissions.getItems()
}
After the JWT strategy is run, I will get the user from repository with roles loaded eagerly. The roles collection entities have permissions collection populated already although they shouldn't be. What am I doing wrong?
User entity:
export default class User extends AggregateRoot < User, 'id' > {
[EntityRepositoryType] ? : UserRepository
// More code here
@ManyToMany({
eager: true,
entity: () => Role,
pivotEntity: () => UserRole,
joinColumn: 'user_id',
inverseJoinColumn: 'role_id'
})
roles ? = new Collection < Role > (this)
}
Role entity
export default class Role extends AggregateRoot < Role, 'id' > {
[EntityRepositoryType] ? : RoleRepository
@Property({
type: 'character',
length: 50,
nullable: false
})
name!: string
@ManyToMany({
eager: false,
entity: () => Permission,
pivotEntity: () => RolePermission,
joinColumn: 'role_id',
inverseJoinColumn: 'permission_id'
})
permissions = new Collection < Permission > (this)
}
The decorator will create a new context and it will be respected with the repositories too. You work with global instances, but they respect the context automatically.
https://mikro-orm.io/docs/identity-map#how-does-requestcontext-helper-work
Note that you should prefer creating the contexts via middleware which is registered automatically by the @mikro-orm/nestjs
package (the forRoot
does it). The decorator way is only for very specific use cases (e.g. cron jobs), and you should generally not use it on a service method, I'd call what you did an antipattern in the first place (but it depends on your app setup, hard to guess).