[BUG] registerProvider doesn't respect Request scope if provider token is a string
Problem description
I'm attempting to restructure an app to allow dynamic selection of DB connection based on request domain (for a multi-tenant SaaS project. My initial thought was to use the "request" scope feature of the DI system. While experimenting with registering dependencies, I found some odd behavior. To demonstrate this, I created a controller with two dependencies: dummy and CONNECTION. Dummy is a class which is also request scoped, and CONNECTION is a constant which is a stand-in for my DB connection.
Expected behavior: Both factory functions for Dummy and CONNECTION are invoked on every request, and not before during app startup.
Actual behavior: The factory function for CONNECTION is invoked only once at app startup, not at the time of request. Dummy's factory function is invoked per-request.
Example
// ================ services.ts =======================
// for registering 3rd party deps and some stuff that's not easily made into a class
export const CONNECTION = 'tenant_db_connection';
registerProvider({
provide: CONNECTION,
scope: ProviderScope.REQUEST,
type: ProviderType.FACTORY,
useFactory () {
console.log('creating connection');
return 'garbage';
},
});
registerProvider({
provide: Dummy,
scope: ProviderScope.REQUEST,
useFactory() {
console.log('constructing new dummy');
return new Dummy();
}
})
// ================ TestController.ts =======================
@Controller('/test')
@Scope(ProviderScope.REQUEST)
export class TestController {
constructor(private dummy: Dummy,
@Inject(CONNECTION) private connString: string
) {
console.log('constructing TestController');
console.log(`connection string is: ${connString}`);
}
@Get('/random')
async getConnectionName() {
return {value: this.dummy.randValue};
}
}
Investigation
At first I assumed that the request scoping was completely broken when using registerProvider, so I created the second class based dependency Dummy and used the @Scope('request') decorator. This worked as expected.
Next, I registered Dummy using registerProvider as shown in the example. To my surprise that also worked, which made me think that it wasn't a problem with registerProvider per se.
After some tinkering, I discovered that changing CONNECTION from a string value to a symbol like Symbol('tenant_db_connection') makes everything work as expected.
Outstanding questions
I have a working solution using Symbols, so I can proceed that way. Is there some reason that using a string as a provider token causes the request scope to be ignored? If so, can we get that documented so that other people don't have to struggle with that quirk?
Information
- Version: 6.126.1
- Packages:
"@hubspot/api-client": "^0.8.0",
"@sendgrid/mail": "^7.0.0",
"@slynova/flydrive": "^1.0.3",
"@slynova/flydrive-s3": "^1.0.3",
"@tsed/common": "^6.126.1",
"@tsed/core": "^6.126.1",
"@tsed/di": "^6.126.1",
"@tsed/exceptions": "^6.126.1",
"@tsed/json-mapper": "^6.126.1",
"@tsed/passport": "^6.126.1",
"@tsed/platform-express": "^6.126.1",
"@tsed/schema": "^6.126.1",
"@tsed/swagger": "^6.126.1",
"@tsed/typeorm": "^6.126.1",
"@types/cookie-parser": "^1.4.2",
"@types/passport-jwt": "^3.0.6",
"axios": "^0.21.4",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"class-transformer": "^0.4.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-session": "^1.17.0",
"form-data": "^2.5.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"moment": "^2.24.0",
"multer": "^1.4.2",
"passport": "^0.4.1",
"passport-http-bearer": "^1.0.1",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"pdf-fill-form": "^5.0.1",
"pg": "^8.5.1",
"pg-hstore": "^2.3.3",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.2.29",
"uuid": "^8.3.2"
hello @supercoffee Thanks for the detailed example!
I'm not sure why String is not considered like a symbol. I'll investigate when I have a time.
Isn't a really priority until you have a solution with Symbol :)
See you Romain
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.