Refactor DI for simplicity and type safety

This commit improves the dependency injection mechanism by introducing a
custom `injectKey` function.

Key improvements are:

- Enforced type consistency during dependency registration and
  instantiation.
- Simplified injection process, abstracting away the complexity with a
  uniform API, regardless of the dependency's lifetime.
- Eliminated the possibility of `undefined` returns during dependency
  injection, promoting fail-fast behavior.
- Removed the necessity for type casting to `symbol` for injection keys
  in unit tests by using existing types.
- Consalidated imports, combining keys and injection functions in one
  `import` statement.
This commit is contained in:
undergroundwires
2023-11-09 13:17:38 +01:00
parent aab0f7ea46
commit 7770a9b521
51 changed files with 398 additions and 177 deletions

View File

@@ -2,38 +2,89 @@ import { InjectionKey, provide, inject } from 'vue';
import { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState';
import { useApplication } from '@/presentation/components/Shared/Hooks/UseApplication';
import { useAutoUnsubscribedEvents } from '@/presentation/components/Shared/Hooks/UseAutoUnsubscribedEvents';
import { useClipboard } from '@/presentation/components/Shared/Hooks/Clipboard/UseClipboard';
import { useCurrentCode } from '@/presentation/components/Shared/Hooks/UseCurrentCode';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { useClipboard } from '../components/Shared/Hooks/Clipboard/UseClipboard';
import { useCurrentCode } from '../components/Shared/Hooks/UseCurrentCode';
import {
AnyLifetimeInjectionKey, InjectionKeySelector, InjectionKeys, SingletonKey,
TransientKey, injectKey,
} from '@/presentation/injectionSymbols';
import { PropertyKeys } from '@/TypeHelpers';
export function provideDependencies(
context: IApplicationContext,
api: VueDependencyInjectionApi = { provide, inject },
) {
const registerSingleton = <T>(key: InjectionKey<T>, value: T) => api.provide(key, value);
const registerTransient = <T>(
key: InjectionKey<() => T>,
factory: () => T,
) => api.provide(key, factory);
const resolvers: Record<PropertyKeys<typeof InjectionKeys>, (di: DependencyRegistrar) => void> = {
useCollectionState: (di) => di.provide(
InjectionKeys.useCollectionState,
() => {
const { events } = di.injectKey((keys) => keys.useAutoUnsubscribedEvents);
return useCollectionState(context, events);
},
),
useApplication: (di) => di.provide(
InjectionKeys.useApplication,
useApplication(context.app),
),
useRuntimeEnvironment: (di) => di.provide(
InjectionKeys.useRuntimeEnvironment,
RuntimeEnvironment.CurrentEnvironment,
),
useAutoUnsubscribedEvents: (di) => di.provide(
InjectionKeys.useAutoUnsubscribedEvents,
useAutoUnsubscribedEvents,
),
useClipboard: (di) => di.provide(
InjectionKeys.useClipboard,
useClipboard,
),
useCurrentCode: (di) => di.provide(
InjectionKeys.useCurrentCode,
() => {
const { events } = di.injectKey((keys) => keys.useAutoUnsubscribedEvents);
const state = di.injectKey((keys) => keys.useCollectionState);
return useCurrentCode(state, events);
},
),
};
registerAll(Object.values(resolvers), api);
}
registerSingleton(InjectionKeys.useApplication, useApplication(context.app));
registerSingleton(InjectionKeys.useRuntimeEnvironment, RuntimeEnvironment.CurrentEnvironment);
registerTransient(InjectionKeys.useAutoUnsubscribedEvents, () => useAutoUnsubscribedEvents());
registerTransient(InjectionKeys.useCollectionState, () => {
const { events } = api.inject(InjectionKeys.useAutoUnsubscribedEvents)();
return useCollectionState(context, events);
});
registerTransient(InjectionKeys.useClipboard, () => useClipboard());
registerTransient(InjectionKeys.useCurrentCode, () => {
const { events } = api.inject(InjectionKeys.useAutoUnsubscribedEvents)();
const state = api.inject(InjectionKeys.useCollectionState)();
return useCurrentCode(state, events);
});
function registerAll(
registrations: ReadonlyArray<(di: DependencyRegistrar) => void>,
api: VueDependencyInjectionApi,
) {
const registrar = new DependencyRegistrar(api);
Object.values(registrations).forEach((register) => register(registrar));
}
export interface VueDependencyInjectionApi {
provide<T>(key: InjectionKey<T>, value: T): void;
inject<T>(key: InjectionKey<T>): T;
}
class DependencyRegistrar {
constructor(private api: VueDependencyInjectionApi) {}
public provide<T>(
key: TransientKey<T>,
resolver: () => T,
): void;
public provide<T>(
key: SingletonKey<T>,
resolver: T,
): void;
public provide<T>(
key: AnyLifetimeInjectionKey<T>,
resolver: T | (() => T),
): void {
this.api.provide(key.key, resolver);
}
public injectKey<T>(key: InjectionKeySelector<T>): T {
const injector = this.api.inject.bind(this.api);
return injectKey(key, injector);
}
}