NestJS Standalone app can't inject Sequelize instance using connection token - dependency-injection

I'm still quite new to NestJS. I'm trying to implement a standalone app that connect to both external/remote source DB and the app DB.
Now I got stuck at Nest can't resolve dependencies of the SourceDbQueryService (ModuleRef, ?). Please make sure that the argument {{token}} at index [1] is available in the EtlModule context.
The {{token}} here is supposedly a string returned from getConnectionToken(connectionName), ex.: sourceDbConnection when connectionName = sourceDb
Here are my modules setup example:
/src/db/source-db-module.ts
#Module({
imports: [
ConfigModule,
LoggingModule,
SequelizeModule.forRootAsync({
imports: [ConfigModule],
inject: [SourceDbConfig],
useFactory: (config: SourceDbConfig) => {
return {
...config,
name: SourceDbConfig.DefaultConnectionName,
autoLoadModels: false,
}
},
}),
],
exports: [SequelizeModule],
})
export class SourceDbModule {}
/src/jobs/etl-module.ts
#Module({
imports: [
ConfigModule,
LocalDbModule,
/** Contains Local repositories with decorated Models, using connection from LocalDbModule */
RepositoryModule,
SourceDbModule,
SequelizeModule.forFeature([], SourceDbConfig.DefaultConnectionName),
],
providers: [
{
provide: SourceDbQueryService,
inject: [ModuleRef, getConnectionToken(SourceDbConfig.DefaultConnectionName)],
useFactory(moduleRef: ModuleRef, sequelize: Sequelize) {
return new SourceDbQueryService(moduleRef, sequelize)
},
},
],
exports: [SourceDbQueryService],
})
export class EtlModule {}
/src/jobs/test-query-source-db.ts
async function bootstrap(): Promise<void> {
try {
const appContext = await NestFactory.createApplicationContext(EtlModule)
appContext.init()
const sourceDb = appContext.get(SourceDbQueryService)
const totalRecordsCount = await sourceDb.count({
// ...filters,
})
console.log(
`retrieved source DB results: (total items: ${totalItemsCount})`
)
appContext.close()
} catch (err) {
console.error(err)
process.exit(-1)
}
}
bootstrap()
Please help, what am I missing here?
Thanks!

Update: Workaround
For now I'm using a workaround by providing Sequelize instance directly from my own factory like this:
/src/db/source-db-module.ts
#Module({
imports: [
ConfigModule,
LoggingModule,
],
providers: [
// WORKAROUND: For SequelizeModule.forRootAsync() injection by connection token not working
{
provide: getConnectionToken(SourceDbConfig.DefaultConnectionName),
inject: [SourceDbConfig],
useFactory(config: SourceDbConfig) {
const { host, port, username, password, database, dialect } = config
return new Sequelize({
host,
port,
username,
password,
database,
dialect,
})
},
},
{
provide: SourceDbQueryService,
inject: [ModuleRef, SourceDbConfig, getConnectionToken(SourceDbConfig.DefaultConnectionName)],
useFactory(moduleRef: ModuleRef, config: SourceDbConfig, sequelize: Sequelize) {
const { schema, viewName } = config
return new SourceDbQueryService(moduleRef, sequelize, { schema, viewName })
},
},
],
exports: [
getConnectionToken(SourceDbConfig.DefaultConnectionName),
SourceDbQueryService,
],
})
export class SourceDbModule {}
/src/jobs/etl-module.ts
#Module({
imports: [
ConfigModule,
LocalDbModule,
/** Contains Local repositories with decorated Models, using connection from LocalDbModule */
RepositoryModule,
SourceDbModule,
],
})
export class EtlModule {}

Related

Nestjs can't create dynamic module with config import

I am trying to create a DynamicModule in Nestjs, but it seems I can't properly use useFactory to inject ConfigModule in the process.
If I use a hardcoded boolean instead of config.get('cache').enabled everything works as expected, but I receive the following error if I try to use config:
TypeError: Cannot read properties of undefined (reading 'get')
Here's the code I arranged so far:
app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validate,
}),
CoreModule.registerAsync({
useFactory: (config: ConfigService) => ({
cacheEnabled: config.get('cache').enabled,
}),
imports: [ConfigModule],
injects: [ConfigService],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
core.module.ts
#Module({})
export class CoreModule {
static registerAsync = (options: {
useFactory: (...args: any[]) => { cacheEnabled: boolean };
imports: any[];
injects: any[];
}): DynamicModule => {
const imports = [];
const providers = [];
if (options.useFactory().cacheEnabled) imports.push(HttpCacheModule);
return {
module: CoreModule,
imports,
providers,
};
};
}

ReferenceError: document is not defined. Service worker. Workbox

I'm learning how to write code of service worker and stuck with the error "ReferenceError: document is not defined" in my app.js file. I'm using workbox library with InjectManifest mode. I think the problem in the webpack.config.js, because when I delete InjectManifest in webpack.config.js the error disappears.
My webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const {InjectManifest} = require('workbox-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.html$/i,
use: [
{
loader: 'html-loader',
},
],
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader',
],
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
],
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new InjectManifest({
swSrc: './src/js/service.worker.js',
swDest: 'service.worker.js',
}),
],
};
My service.worker.js file:
import { precacheAndRoute } from 'workbox-precaching/precacheAndRoute';
import { cinemaNews } from './cinemaNews';
import { url } from './app';
precacheAndRoute(self.__WB_MANIFEST);
const CACHE_NAME = 'v1';
const responseCache = new Response(JSON.stringify(cinemaNews));
self.addEventListener('install', (evt) => {
console.log('install')
evt.waitUntil((async () => {
console.log('install waitUntil')
const cache = await caches.open(CACHE_NAME);
await cache.put(url, responseCache);
await self.skipWaiting();
})());
});
self.addEventListener('activate', (evt) => {
console.log('activate')
evt.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', (evt) => {
console.log('sw fetch')
const requestUrl = new URL(evt.request.url);
if (!requestUrl.pathname.startsWith('/news')) return;
evt.respondWith((async () => {
console.log('respondWith')
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(evt.request);
return cachedResponse;
})());
evt.waitUntil((async () => {
console.log('waitUntil');
const response = await fetch(evt.request.url);
const client = await clients.get(evt.clientId);
let json = await response.json();
client.postMessage(json);
})());
});
This statement:
import { url } from './app';
appears to be triggering the issue, as there must be code inside of your app.js that is executed via that import, and which assumes that document will be defined. (It's not defined inside of the ServiceWorkerGlobalScope.)
Based on how you're using the export, I'm assuming that it's just a string constant containing a shared URL that you want to use from both your main web app and your service worker. Assuming that's the case, the easiest thing to do would be to refactor your modules that there's a constants.js (or some similar name) module that only exports your string constants, and doesn't try to run any code that references document. You can then import the constant from either your web app or the service worker without issue.
// constants.js
export const url = '/path/to/url';
// service-worker.js
import {url} from './constants';
// do something with url
// app.js
import {url} from './constants';
// do something with url

NestJS E2E tests with Jest. Injected service returns undefined (only tests)

I have a problem with the end-to-end testing of my users module. I want to validate if there is a "companyCode" when a user makes a GET request in /users and sends this code in the query params. This validator searches the database if this company code exists, if it does not exist it returns an error. The problem is that in the test this validation doesn't happen, because "companiesService" returns undefined (only in the test), what's missing?
Possible Solution: something related to useContainer(class-validator).
Thanks.
users.e2e-spec.ts
describe('UsersController (e2e)', () => {
let app: INestApplication;
let repository: Repository<User>;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [UsersModule, AuthModule, TypeOrmModule.forRoot(ormConfig)],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
useContainer(app.select(UsersModule), { fallbackOnErrors: true });
repository = module.get('UserRepository');
await app.init();
});
afterAll(async () => {
await app.close();
});
describe('/users (GET)', () => {
it('should return users if requesting user sent "companyCode" in the request body', async (done) => {
return request(app.getHttpServer())
.get('/users')
.auth('admin', 'admin')
.query({ companyCode: '2322661870558778503' }) // should return 200 because companyCode exists but is returning 400
.expect(200)
.then((res) => {
expect(res.body.users).toHaveLength(1);
done();
})
.catch((err) => done(err));
});
});
});
users.module.ts
#Module({
controllers: [UsersController],
providers: [UsersService, UserExistsRule],
imports: [
TypeOrmModule.forFeature([
User,
Person,
Type,
Profile,
UserProfile,
Company,
]),
CompaniesModule,
],
exports: [UsersService],
})
export class UsersModule {}
read-users.dto.ts
export class ReadUsersDto {
#IsOptional()
#IsNotEmpty()
#IsString()
#MinLength(1)
#MaxLength(255)
public name?: string;
#IsOptional()
#IsNotEmpty()
#IsNumberString()
#Type(() => String)
#Validate(CompanyExistsRule)
public companyCode?: string;
}
companies.module.ts
#Module({
providers: [CompaniesService, CompanyExistsRule],
imports: [TypeOrmModule.forFeature([Company, Person])],
exports: [CompaniesService],
})
export class CompaniesModule {}
companies.decorator.ts
#ValidatorConstraint({ name: 'CompanyExists', async: true })
#Injectable()
export class CompanyExistsRule implements ValidatorConstraintInterface {
constructor(private companiesService: CompaniesService) {}
async validate(code: string) {
try {
console.log('companiesService', this.companiesService); // returns undefined on test
await this.companiesService.findOneByCode(code);
} catch (e) {
return false;
}
return true;
}
defaultMessage() {
return `companyCode doesn't exist`;
}
}
I found that I imported useContainer from typeorm instead of the class-validator hahahahha.
// incorrectly imported
import { useContainer } from 'typeorm';
// correctly imported
import { useContainer } from 'class-validator';

Nestjs - Typeorm custom connection name

I have a Nestjs db Module and it works perfectly
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: () => {
return {
name: 'default', // <=== here
type: "mysql",
...
};
},
}),
TypeOrmModule.forFeature(entities, 'default'), // <=== here
],
exports: [TypeOrmModule],
})
export class DBModule {}
if I change the connection name to anything else rather then 'default' say 'test' I get an error
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: () => {
return {
name: 'test', // <=== here
type: "mysql",
...
};
},
}),
TypeOrmModule.forFeature(entities, 'test'), // <=== here
],
exports: [TypeOrmModule],
})
export class DBModule {}
[Nest] 10746 - 05/15/2021, 5:55:34 PM [ExceptionHandler] Nest can't resolve dependencies of the test_UserEntityRepository (?). Please make sure that the argument testConnection at index [0] is available in the TypeOrmModule context.
Potential solutions:
- If testConnection is a provider, is it part of the current TypeOrmModule?
- If testConnection is exported from a separate #Module, is that module imported within TypeOrmModule?
#Module({
imports: [ /* the Module containing testConnection */ ]
})
The error seams to only show up if I use TypeOrmModule.forRootAsync
For TypeOrmModule.forRoot if works!
Is there any different way to indicate the connection name? I need to add another connection and can't do it because of this error. Really would like to use 'forRootAsync'
Pass the connection name as follows.
#Module({
imports: [
TypeOrmModule.forRootAsync({
name: 'test', // <=== here
useFactory: () => {
return {
type: "mysql",
...
};
},
}),
TypeOrmModule.forFeature(entities, 'test'), // <=== here
],
exports: [TypeOrmModule],
})
export class DBModule {}

NestJs: Dynamically Create Instances of Class

Instead of Singletons, I want to create dynamically class instances in NestJs.
I found two ways:
1) Directly create the class (ChripSensor is then not #Injectable)
import { ChirpSensor } from './chirp-sensor/chirp-sensor';
#Injectable()
export class SensorsService {
registeredSensors: any;
constructor(
#InjectModel('Sensor') private readonly sensorModel: Model<ISensor>,
private i2cService: I2cService) {
const sensors = this.i2cService.getSensors();
sensors.forEach((sensor) => {this.registeredSensors[sensor._id] = new ChirpSensor({name: sensor.name})});
}
I'm wondering if that is consistent with the DI way of nest.js
2) The second solution would be via a factory, but here I don't know how to pass the options.
export const chirpFactory = {
provide: 'CHIRP_SENSOR',
useFactory: (options) => {
console.log('USING FACTORY CHIRP, options', options)
if (process.env.SIMULATION === 'true') {
return new ChirpSensorMock(options);
}
else {
return new ChirpSensor(options);
}
}
};
Not quite sure how to continue here/ inject the factory properly as the examples create the object in the constructor without options?
Question:
What is the NestJs way to create those class instances?
Edit - for B12Toastr
Module - get the Mock or Original on Compile time
providers: [
{
provide: 'CHIRP_SENSOR',
useValue: process.env.SIMULATION === 'true'
? ChirpSensorMock
: ChirpSensor
},
],
Sensor Service
#Injectable()
export class SensorsService {
registeredSensors: any;
constructor(
#Inject('CHIRP_SENSOR') private ChirpSensorClass: any, // any works but ChirpSensorMock | ChirpSensor not
private i2cService: I2cService
) {
const sensors = this.i2cService.getSensors();
sensors.forEach((sensor) => {this.registeredSensors[sensor._id] = new ChirpSensorClass({name: sensor.name})});
}
You can pass options to your factory via DI via useValue or useClass
providers: [
{
provide: MyOptions,
useValue: options
},
{
provide: 'CHIRP_SENSOR',
useFactory: (options: MyOptions) => {
console.log('USING FACTORY CHIRP, options', options);
if (process.env.SIMULATION === 'true') {
return new ChirpSensorMock(options);
} else {
return new ChirpSensor(options);
}
},
},
],
Alternatively, you could also avoid using a factory altogether and make the decision which class to use at compile time via:
providers: [
{
provide: MyOptions,
useValue: options
},
{
provide: 'CHIRP_SENSOR',
useValue: process.env.SIMULATION === 'true'
? ChirpSensorMock
: ChirpSensor
},
],
or simply:
providers: [
{
provide: MyOptions,
useValue: options
},
{
process.env.SIMULATION === 'true' ? ChirpSensorMock : ChirpSensor
},
],
In case you are not using a factory as described above, you would then inject the options in your ChirpSensor (or the Mocked Sensor)` using typical constructor-based dependency injection:
#Injectable()
export class ChripSensor {
constructor(#inject(MyOptions) private options: MyOptions) {
}
// ...
}
Depending on whether your options are wrapped in a class or a simple object you would either use useValue or useClass. With useClass you have to write less code and do not have to use the #Inject decorator since the class itself is used as DI token. However, it seems if MyOptions is a class, you do not need to use #Inject in any case to inject the dependency because NestJS uses the class as DI token, regardless whether you used useValue or useClass to provide the dependency...

Resources