@homer0/jimple
Version:
An extended version of the Jimple lib, with extra features
125 lines (119 loc) • 4.39 kB
text/typescript
import type { Jimple } from '../jimple/index.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- They are dynamically extended.
type GenericDict = Record<any, any>;
/**
* A helper that reusable services can use to resolve dependencies in constructors and/or
* providers.
*
* @template InjectDictionary A dictionary of the dependencies and their types.
* @template InjectKey The literal type of the dictionary types.
*/
export class InjectHelper<
InjectDictionary extends GenericDict,
InjectKey = keyof InjectDictionary,
> {
/**
* This method is meant to be used to validate the dependencies a service receives, and
* needs.
* It will check on the received dependencies, if a specific dependency exists, it will
* return it, otherwise, it will create a new instance.
*
* @param dependencies The dependencies received by the implementation.
* @param key The key of the dependency to validate.
* @param init A function to create a dependency in case it doesn't exist in
* the dictionary.
* @returns An instance of the dependency.
* @template DepKey The literal key of the dependency to validate.
* @template DepType The type of the dependency, obtained from the dictionary sent
* to the constructor.
* @example
*
* type Dependencies = {
* dep: string;
* };
* const helper = new InjectHelper<Dependencies>();
*
* type MyServiceOptions = {
* inject?: Partial<Dependencies>;
* };
* const myService = ({ inject = {} }: MyServiceOptions) => {
* const dep = helper.get(inject, 'dep', () => 'default');
* console.log('dep:', dep);
* };
*
*/
get<DepKey extends InjectKey, DepType = InjectDictionary[DepKey]>(
dependencies: Partial<InjectDictionary>,
key: InjectKey,
init: () => DepType,
): DepType {
const existing = dependencies[key];
if (typeof existing !== 'undefined') {
return existing!;
}
return init();
}
/**
* This is meant to be used in a provider creator to resolve dependencies' names sent as
* options. For each dependency, the method will first check if a new name was
* specified, and try to get it with that name, otherwise, it will try to get it with
* the default name, and if it doesn't exist, it will just keep it as `undefined` and
* expect the service implements {@link InjectHelper.get} to ensure the dependency.
*
* @param dependencies The dependencies needed by the service.
* @param container A reference to the Jimple container.
* @param inject A dictionary of dependencies names.
* @returns A dictionary of dependencies to send to the service.
* @template DepKey The literal key of the dependencies to validate.
* @template Container The type of the Jimple container.
* @example
*
* type Dependencies = {
* dep: string;
* };
* const helper = new InjectHelper<Dependencies>();
*
* type MyProviderOptions = {
* services?: {
* [key in keyof Dependencies]?: string;
* };
* };
*
* const myProvider = providerCreator(
* ({ services = {} }: MyProviderOptions) =>
* (container) => {
* container.set('myService', () => {
* const inject = helper.resolve(['dep'], container, services);
* return myService({ inject });
* });
* },
* );
*
*/
resolve<DepKey extends InjectKey, Container extends Jimple>(
dependencies: DepKey[],
container: Container,
inject: Partial<Record<keyof InjectDictionary, string>>,
): Partial<InjectDictionary> {
const result = dependencies.reduce<Partial<InjectDictionary>>((acc, key) => {
if (inject[key]) {
acc[key] = container.get<InjectDictionary[typeof key]>(inject[key]!);
return acc;
}
const keyStr = key as unknown as string;
if (container.has(keyStr)) {
acc[key] = container.get<InjectDictionary[typeof key]>(keyStr);
return acc;
}
return acc;
}, {});
return result;
}
}
/**
* Shorthand for `new InjectHelper()`.
*
* @returns A new instance of {@link InjectHelper}.
*/
export const injectHelper = <InjectDictionary extends GenericDict>() =>
new InjectHelper<InjectDictionary>();