Map Providers / Interface Providers / Array Providers
darkbasic opened this issue · 0 comments
While rearchitecting accounts.js 1.0 I've played around the concept of Map Providers.
Basically in Accounts.js we have multiple types of authentication services (password, magic link, oauth, sms, etc..) which implement the same known interface and while most of them are hosted in the monorepo the design allows the user to create and provide its own authentication service. That means that injecting all of them into shared services like AccountsServer
(which is part of the core module) is not an option, because this way the user would not be allowed to create its own auth service.
To solve this issue I'd like to introduce the concept of Map Providers: a type of provider which acts similarly to Javascript Maps where the map is constructed by declaring new providers which add certain keys to the Map.
Basically you would inject the AuthenticationServicesToken
like this:
export const AuthenticationServicesToken = new InjectionToken<AuthenticationServices>(
'AuthenticationServices'
);
// AuthenticationServices is either a Map or a Map-like object
constructor(@Inject(AuthenticationServicesToken) public services: AuthenticationServices<CustomUser>) {
and other modules like the password module would provide the various elements like this:
{
provide: AuthenticationServicesToken,
useMap: ['password', AccountsPassword], // [key, value] tuple
},
Another way we could look at this is the concept of Interface Providers, where each element of the Map must implement a known interface in order to extend the associated provider:
// services is a Map/Map-like Object where each element of the Map implements the AuthenticationServiceInterface
constructor(@Inject(AuthenticationServiceInterface) public services: Map<string, AuthenticationServiceInterface<CustomUser>>) {
{
provide: AuthenticationServiceInterface,
useInterface: ['password', AccountsPassword], // [key, value] tuple, AccountsPassword implements AuthenticationServiceInterface
},
The downside of this approach is that I'm not sure if you can use Typescript interfaces as Injection Tokens.
Instead of a Map/Map-like object we could even inject an array:
// services is an array
constructor(@Inject(AuthenticationServiceInterface) public services: AuthenticationServiceInterface<CustomUser>[]) {
{
provide: AuthenticationServiceInterface,
useInterface: AccountsPassword, // AccountsPassword implements AuthenticationServiceInterface
},
But the downside of this approach is that you will have to go through each and every element of the array every time you look for a certain authentication service because you won't have a key.
With the current feature set the various modules which implement the different authentication services cannot provide the new authentication service on their own: after including the module the user has to manually provide the AuthenticationServicesToken
.