import { Provider } from "@angular/core"; import { Constructor, Opaque } from "type-fest"; import { SafeInjectionToken } from "./safe-injection-token"; /** * The return type of the {@link safeProvider} helper function. * Used to distinguish a type safe provider definition from a non-type safe provider definition. */ export type SafeProvider = Opaque; // TODO: type-fest also provides a type like this when we upgrade >= 3.7.0 type AbstractConstructor = abstract new (...args: any) => T; type MapParametersToDeps = { [K in keyof T]: AbstractConstructor | SafeInjectionToken; }; type SafeInjectionTokenType = T extends SafeInjectionToken ? J : never; /** * Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken */ type ProviderInstanceType = T extends SafeInjectionToken ? InstanceType> : T extends Constructor | AbstractConstructor ? InstanceType : never; /** * Represents a dependency provided with the useClass option. */ type SafeClassProvider< A extends AbstractConstructor | SafeInjectionToken, I extends Constructor>, D extends MapParametersToDeps>, > = { provide: A; useClass: I; deps: D; }; /** * Represents a dependency provided with the useValue option. */ type SafeValueProvider, V extends SafeInjectionTokenType> = { provide: A; useValue: V; }; /** * Represents a dependency provided with the useFactory option. */ type SafeFactoryProvider< A extends AbstractConstructor | SafeInjectionToken, I extends (...args: any) => ProviderInstanceType, D extends MapParametersToDeps>, > = { provide: A; useFactory: I; deps: D; multi?: boolean; }; /** * Represents a dependency provided with the useExisting option. */ type SafeExistingProvider< A extends Constructor | AbstractConstructor | SafeInjectionToken, I extends Constructor> | AbstractConstructor>, > = { provide: A; useExisting: I; }; /** * Represents a dependency where there is no abstract token, the token is the implementation */ type SafeConcreteProvider< I extends Constructor, D extends MapParametersToDeps>, > = { provide: I; deps: D; }; /** * If useAngularDecorators: true is specified, do not require a deps array. * This is a manual override for where @Injectable decorators are used */ type UseAngularDecorators = Omit & { useAngularDecorators: true; }; /** * Represents a type with a deps array that may optionally be overridden with useAngularDecorators */ type AllowAngularDecorators = T | UseAngularDecorators; /** * A factory function that creates a provider for the ngModule providers array. * This (almost) guarantees type safety for your provider definition. It does nothing at runtime. * Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator, * however this cannot be enforced by the type system and will not cause an error if the decorator is not used. * @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] }) * @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.) * @returns The exact same object without modification (pass-through). */ export const safeProvider = < // types for useClass AClass extends AbstractConstructor | SafeInjectionToken, IClass extends Constructor>, DClass extends MapParametersToDeps>, // types for useValue AValue extends SafeInjectionToken, VValue extends SafeInjectionTokenType, // types for useFactory AFactory extends AbstractConstructor | SafeInjectionToken, IFactory extends (...args: any) => ProviderInstanceType, DFactory extends MapParametersToDeps>, // types for useExisting AExisting extends Constructor | AbstractConstructor | SafeInjectionToken, IExisting extends | Constructor> | AbstractConstructor>, // types for no token IConcrete extends Constructor, DConcrete extends MapParametersToDeps>, >( provider: | AllowAngularDecorators> | SafeValueProvider | AllowAngularDecorators> | SafeExistingProvider | AllowAngularDecorators> | Constructor, ): SafeProvider => provider as SafeProvider;