How can I get redis io client from NestJS CacheManager module - nestjs

I am currently using cache manager module from NestJS and I was wondering why I can't get the NodeRedis client like this :
constructor(
#Inject(CACHE_MANAGER) private cacheManager: Cache,
) {
cacheManager.store.getClient();
}
I am getting this error :
ERROR in [...].controller.ts:24:24
TS2339: Property 'getClient' does not exist on type 'Store'.
22 | #Inject(CACHE_MANAGER) private cacheManager: Cache,
23 | ) {
> 24 | cacheManager.store.getClient();
| ^^^^^^^^^
25 | }
I did configured the cache-manager-redis-store when I registered the CacheModule then I supposed I could get the client.

tl;dr It seems that cache-manager-redis-store isn't properly supported by TypeScript because the RedisCache type is private and can't be imported.
As a workaround, you could copy the private types to your own file:
import { CACHE_MANAGER, Inject, Injectable } from '#nestjs/common';
import { Store } from 'cache-manager';
import Redis from 'redis';
interface RedisCache extends Cache {
store: RedisStore;
}
interface RedisStore extends Store {
name: 'redis';
getClient: () => Redis.RedisClient;
isCacheableValue: (value: any) => boolean;
}
#Injectable()
export class CacheService {
constructor(
#Inject(CACHE_MANAGER)
private cacheManager: RedisCache,
) {
cacheManager.store.getClient();
}
}
Deep Dive
It looks like the CACHE_MANAGER provided by NestJS is created by createCacheManager, which imports the npm package cache-manager and then invokes its caching function with the store package you give.
I think the Cache type you're using is imported from cache-manager. The type is defined here and contains a Store contained here in the same file. getClient is not a method method on that interface, so the error message is correct.
However, since you're using an external package for the store, there's more to it than caching-manager knows about. Looking at the types for cache-manager-redis-store, you can see there's a type RedisStore that extends Store and includes getClient.
So cacheManager technically has getClient since you've configured it with the redis store package, but you need to set the type on your cacheManager variable to RedisCache for TypeScript to allow it.
Based on the DefinitelyTyped types for cache-manager-redis-store, it seems your type should be redisStore.CacheManagerRedisStore.RedisCache if you imported the package as redisStore, but there seems to be an issue because that namespace CacheManagerRedisStore is not exported.
There's an issue about this same problem on the repo and there's an issue asking for TypeScript support. It seems this package isn't properly supported by TypeScript at this time.

Related

How to create a factory for a typeorm custom repository in nestjs?

I have a repository:
export class MyRepository
extends
Repository<MyEntity>
{
constructor(
protected readonly _clientId: string
) {
super()
}
// ... methods
}
I need to pass the client id through which isnt known until request time. As such, the only way I know how to do it would be to create a factory which can create the repo at run time (it's in GRPC metadata).
#Injectable()
export class MyRepositoryFactory {
create(clientId: string) {
return new MyRepository(
clientId,
);
}
}
I register this as a provider and then in my controller I call:
const { clientId } = context;
const repository = this.myRepositoryFactory.create(clientId);
However I get the error
"Cannot read property 'findOne' of undefined"
when trying to do a basic typeorm call. I can see this is because instead the repository should be registered in the module imports like:
imports: [
TypeOrmModule.forFeature([ MyRepository, MyEntity ])
],
However this only works when injecting the repository directly, not in a factory. I have no idea how to either overcome this problem, or use a different way of creating the repository at run time with GRPC meta data passed through. Any help would be appreciated, thanks.
you cant call new and have the repository connected to TypeORM.
for that you need to make call to getCustomRepository(MyCustomRepository (or connectio.getCustomRepository) so it would be connected to the connectiong pool and everything TypeORM hides under the hood.
IMO creating a custom repository per request is not such a great idea, maybe you can create a Provider that has scope REQUEST (so the provider would be created per-request) allowing NestJS to inject the repository the 'standard' way, modify it's client attribute and then use it's methods that uses the internal repository methods.
something like
#Injectable({ scope: Scope.REQUEST })
export class RequestRepositoryHandler {
client_id: string
constructor(#InjectRepository(MyEntity) private repo: Repository<MyEntity>){}
// other methods
}
// usage
#Injectable()
export class SomeService {
constructor(private providerRepo: RequestRepositoryHandler){}
async method(client_id: string){
this.providerRepo.client_id = client_id; // the provider is per-request
// now do some work
}
}

extend class from an external library and inherit its context in nestjs

I have an external library and I am creating a service so that I can extend my service to that class and use its context.
class testApi {
constructor(standardAxios, cachedAxios = null) {
this.axios = standardAxios;
this.cachedAxios = cachedAxios;
}
setAxios(standardAxios, cachedAxios = null) {
this.axios = standardAxios;
this.cachedAxios = cachedAxios;
return this;
}
}
module.exports = testApi;
this is the service I am creating and I can't access the axios context it has created
export class UsersService extends testApi {
constructor(#InjectModel(User.name) private userModel: Model<UserDocument>, #Inject(axios)) {
super();
}
async test(): Promise<any> {
this.axios
.get('https://jsonplaceholder.typicode.com/todos/1')
.then((e) => console.log(e));
}
}
Assuming_ you have a provider token for the variable axios, this isn't how you inject it. Look at your #InjectModel(): notice how it has a private userModel: Model<UserDocument> after it? That's how your tell Typescript what the class variable is. If you don't need a class member, that's fine, but you still need the constructor parameter to be defined. This means, at the very minimum you need #Inject(axios) axios: any. You can set the type, I believe it would be AxiosInstance instead of any. Now you can pass axios on to super.
Do note that to do this in the first place, you must have a custom provider like
{
provide: axios, // same variable as is being passed to `#Inject()`
useValue: axiosInstance // the actual instance to be passed
}
And this must be in the providers array of the module you're currently working in. This is called a custom provider.
These are some of the core concepts of NestJS, being able to inject via tokens and extending classes, so I suggest you read up on Typescript and go through the docs again to get a better understanding of what's available and how classes inherit from each other.

Iam getting-TypeError: Class extends value undefined is not a constructor or null

Iam creating a bot with Microsoft bot framework which has to be integrated with MS Teams.Iam getting the following error-TypeError: Class extends value undefined is not a constructor or null on compiling the code,when i extend TeamsActivityHandler class to overwrite the onmessage method.
The code works fine with ActivityHandler class though.
The code below throws an error
const { TeamsActivityHandler} = require('botbuilder');
class mybot extends TeamsActivityHandler {
constructor() {
super();
}
bunch of other codes here.
}
where as this below one works fine
const { ActivityHandler} = require('botbuilder');
class mybot extends ActivityHandler {
constructor() {
super();
}
bunch of other codes here.
}
Hilton is correct, TeamsActivityHandler is new-ish (4.6.0+ like Trinetra said), I would look at your packages.
Along with the smaller dependencies, and various packages, a bot that uses TeamsActivityHanlder needs the following:
botbuilder
botbuilder-core
botframework-connector
botframework-schema
Most of these are installed as dependencies, so if you npm install botbuilder, you'll catch the rest.
You'll note 'Botbuilder-teams' is NOT on that list. Botbuilder-teams is deprecated here I saw from your comment that you said you'd updated that package. If you're following a tutorial or example that uses it, you're using an outdated resource. Please look into the Teams-based samples on the Botframework repo that Trinetra-MSFT referenced.

Log Session User NodeJs NestJs

I am trying to log the session user but its not working as i suspected. I was thinking i could add it in the middle ware but clearly this wont work. Is there a strategy or pattern i should be using to accomplish this.
The PassportAuthInterceptor log should have distinct users but its only using the last one set by the Middleware
I see them using it in the log4js documentation but clearly this doesnt seem to make sense the way i demonstrated below.
I also see that the have a AuthLibrary.currentUser() call, but im unsure how to accomplish this.
https://github.com/log4js-node/log4js-node/blob/master/docs/layouts.md#tokens
seeing as how this is NestJS, can i inject the user into a service somehow as that could solve my problem.
export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new AppLoggerService(LoggerMiddleware.name);
public use(req, res, next: () => void) {
log4js.getLogger('default').addContext('user', user.authName)
next();
}
}
So this is kinda gross but it works, i created 2 loggers one global (normal) and one session (scope request). The thing is that any class that is used by passport login cannot reference the SessionLoggerService .. This will have me split classes as global and Session so the classes they call can use the correct logger.
https://github.com/nestjs/nest/issues/1870
#Injectable({scope: Scope.REQUEST})
export class SessionLoggerService implements LoggerService {
public static logger = undefined;
constructor(#Inject(REQUEST) private readonly req) {
SessionLoggerService.logger = log4js.getLogger('user');
const user = this.req && this.req.user ? this.req.user : null;
if (user) {
SessionLoggerService.logger.addContext('user', user.authName);
} else {
SessionLoggerService.logger.addContext('user', '');
}
}
....
}

NestJS: How to register transient and per web request providers

I am working on a multi-tenant application using NestJS and offering my API through their GraphQL module. I would like to know how I could tell NestJS to instantiate my providers per web request. According to their documentation providers are singleton by default but I could not find a way to register transient or per request providers.
Let me explain a specific use case for this. In my multi-tenant implementation, I have a database per customer and every time I receive a request in the backend I need to find out for which customer it is so I need to instantiate services with a connection to the right database.
Is this even possible using NestJS?
With the release of nest.js 6.0, injection scopes were added. With this, you can choose one of the following three scopes for your providers:
SINGLETON: Default behavior. One instance of your provider is used for the whole application
TRANSIENT: A dedicated instance of your provider is created for every provider that injects it.
REQUEST: For each request, a new provider is created. Caution: This behavior will bubble up in your dependency chain. Example: If UsersController (Singleton) injects UsersService (Singleton) that injects OtherService (Request), then both UsersController and UsersService will automatically become request-scoped.
Usage
Either add it to the #Injectable() decorator:
#Injectable({ scope: Scope.REQUEST })
export class UsersService {}
Or set it for custom providers in your module definition:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
Outdated Answer
As you can see in this issue, nestjs does not yet offer a built-in solution for request-scoped providers. But it might do so in the near future:
Once async-hooks feature (it is still experimental in node 10) is
stable, we'll think about providing a built-in solution for
request-scoped instances.
I was struggling with similar issue, and one way to achieve this is to use node-request-context module as a global request register, that will give you the request context. So you will not have separate service instances, but you can ask this static register to give you request specific instance/connection.
https://github.com/guyguyon/node-request-context
Create simple context helper:
import { createNamespace, getNamespace } from 'node-request-context';
import * as uuid from 'uuid';
export class RequestContext {
public static readonly NAMESPACE = 'some-namespace';
public readonly id = uuid.v4();
constructor(public readonly conn: Connection) { }
static create(conn: Connection, next: Function) {
const context = new RequestContext(conn);
const namespace = getNamespace(RequestContext.NAMESPACE) || createNamespace(RequestContext.NAMESPACE);
namespace.run(() => {
namespace.set(RequestContext.name, context);
next();
});
}
static currentRequestContext(): RequestContext {
const namespace = getNamespace(RequestContext.NAMESPACE);
return namespace ? namespace.get(RequestContext.name) : null;
}
static getConnection(): Connection {
const context = RequestContext.currentRequestContext();
return context ? context.conn : null;
}
}
The conn instance parameter is your connection, feel free to put there other request specific dependencies. Also the id there is just for debugging, no real need to use uuid module as I did.
Create middleware wrapper (this allows you to use DI here):
#Injectable()
export class ContextMiddleware implements NestMiddleware {
constructor(private readonly connectionManager: ...) { }
resolve(...args: any[]): MiddlewareFunction {
return (req, res, next) => {
// create the request specific connection here, probably based on some auth header...
RequestContext.create(this.connectionManager.createConnection(), next);
};
}
}
Then register new middleware in your nest application:
const app = await NestFactory.create(AppModule, {});
app.use(app.get(RequestLoggerMiddleware).resolve());
And finally the profit part - get the request specific connection anywhere in your application:
const conn = RequestContext.getConnection();
NestJS has a build-in request-scope dependency injection mechanism
https://docs.nestjs.com/fundamentals/injection-scopes
but it has serious drawbacks according to the documentation:
Scope bubbles up the injection chain. A controller that depends on a request-scoped provider will, itself, be request-scoped.
Using request-scoped providers will have an impact on application performance. While Nest tries to cache as much metadata as possible, it will still have to create an instance of your class on each request. Hence, it will slow down your average response time and overall benchmarking result. Unless a provider must be request-scoped, it is strongly recommended that you use the default singleton scope.
Recently I have created request-scope implementation for NestJS free from bubbling up the injection chain and performance impact.
https://github.com/kugacz/nj-request-scope
To use it first you have to add import of RequestScopeModule in the module class decorator:
import { RequestScopeModule } from 'nj-request-scope';
#Module({
imports: [RequestScopeModule],
})
Next, there are two ways of request-scope injection:
Inject express request object into class constructor with NJRS_REQUEST token:
import { NJRS_REQUEST } from 'nj-request-scope';
[...]
constructor(#Inject(NJRS_REQUEST) private readonly request: Request) {}
Change class inject scope to request-scope with #RequestScope() decorator:
import { RequestScope } from 'nj-request-scope';
#Injectable()
#RequestScope()
export class RequestScopeService {
You can find example implementations in this repository: https://github.com/kugacz/nj-request-scope-example

Resources