feat: types organization and cleaning up code base

This commit is contained in:
Jacob Nguyen
2023-05-09 22:49:29 -05:00
parent cd1568ff69
commit 8a537d670b
39 changed files with 591 additions and 536 deletions

39
src/core/ioc/base.ts Normal file
View File

@@ -0,0 +1,39 @@
import * as assert from "assert";
import { composeRoot, useContainer } from "./dependency-injection";
import { DependencyConfiguration, Dependencies } from "./types";
import { CoreContainer } from "../structures/container";
//SIDE EFFECT: GLOBAL DI
let containerSubject: CoreContainer<Partial<Dependencies>>;
/**
* Returns the underlying data structure holding all dependencies.
* Exposes methods from iti
*/
export function useContainerRaw() {
assert.ok(
containerSubject && containerSubject.isReady(),
"Could not find container or container wasn't ready. Did you call makeDependencies?"
);
return containerSubject;
}
/**
* @since 2.0.0
* @param conf a configuration for creating your project dependencies
*/
export async function makeDependencies<const T extends Dependencies>(
conf: DependencyConfiguration<T>,
) {
//Until there are more optional dependencies, just check if the logger exists
//SIDE EFFECT
containerSubject = new CoreContainer()
await composeRoot(conf);
//SIDE EFFECT
containerSubject.ready();
return useContainer<T>();
}

View File

@@ -0,0 +1,78 @@
import type { CoreDependencies, Dependencies, DependencyConfiguration, MapDeps, IntoDependencies } from './types';
import { DefaultLogging } from '../structures';
import { SernError } from '../structures/errors';
import { useContainerRaw } from './base';
import { CoreContainer } from '../structures/container';
/**
* @__PURE__
* @since 2.0.0.
* Creates a singleton object.
* @param cb
*/
export function single<T>(cb: () => T) {
return cb;
}
/**
* @__PURE__
* @since 2.0.0
* Creates a transient object
* @param cb
*/
export function transient<T>(cb: () => () => T) {
return cb;
}
export function Service(key: string): unknown
export function Service<T extends keyof Dependencies>(key: T) {
return useContainerRaw().get(key)!
}
export function Services<const T extends (keyof Dependencies)[]>(...keys: [...T]) {
const container = useContainerRaw();
return keys.map(k => container.get(k)!) as IntoDependencies<T>
}
/**
* Given the user's conf, check for any excluded dependency keys.
* Then, call conf.build to get the rest of the users' dependencies.
* Finally, update the containerSubject with the new container state
* @param conf
*/
export async function composeRoot<T extends Dependencies>(conf: DependencyConfiguration<T>) {
//container should have no client or logger yet.
const excludeLogger = conf.exclude?.has('@sern/logger');
const container = useContainerRaw();
if (!excludeLogger) {
container.upsert({
'@sern/logger': () => new DefaultLogging(),
});
}
//Build the container based on the callback provided by the user
const updatedContainer = await conf.build(container as CoreContainer<CoreDependencies>);
try {
updatedContainer.get('@sern/client');
} catch {
throw new Error(SernError.MissingRequired + " No client was provided")
}
if (!excludeLogger) {
updatedContainer.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
}
}
export function useContainer<const T extends Dependencies>() {
console.warn(`
Warning: using a container hook is not recommended.
Could lead to many unwanted side effects.
Use the new Service(s) api function instead.
`
);
return <V extends (keyof T)[]>(...keys: [...V]) =>
keys.map(key => useContainerRaw().get(key as keyof Dependencies)) as MapDeps<T, V>;
}

3
src/core/ioc/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export { useContainerRaw, makeDependencies } from './base';
export { Service, Services, single, transient } from './dependency-injection';
export type { Singleton, Transient } from './types'

47
src/core/ioc/types.ts Normal file
View File

@@ -0,0 +1,47 @@
import { Container, UnpackFunction } from "iti";
import { Awaitable, ModuleStore } from "../../shared";
import { ErrorHandling, Logging, ModuleManager } from "../contracts";
import { SernEmitter } from "../";
import EventEmitter from "node:events";
export type Singleton<T> = () => T;
export type Transient<T> = () => () => T;
export interface CoreDependencies {
'@sern/logger'?: Singleton<Logging>;
'@sern/emitter': Singleton<SernEmitter>;
'@sern/store': Singleton<ModuleStore>;
'@sern/modules': Singleton<ModuleManager>;
'@sern/errors': Singleton<ErrorHandling>;
}
export interface Dependencies extends CoreDependencies {
'@sern/client': Singleton<EventEmitter>;
}
export type DependencyFromKey<T extends keyof Dependencies> = Dependencies[T];
export type IntoDependencies<Tuple extends [...any[]]> = {
[Index in keyof Tuple]: UnpackFunction<DependencyFromKey<Tuple[Index]>&{}>; //Unpack and make NonNullable
} & { length: Tuple['length'] };
export interface DependencyConfiguration<T extends Dependencies> {
//@deprecated. Loggers will always be included in the future
exclude?: Set<'@sern/logger'>;
build: (root: Container<CoreDependencies, {}>) => Awaitable<Container<T, {}>>;
}
//To be removed in future
//prettier-ignore
export type MapDeps<Deps extends Dependencies, T extends readonly unknown[]> = T extends [
infer First extends keyof Deps,
...infer Rest extends readonly unknown[],
]
? [
UnpackFunction<Deps[First]>,
...(MapDeps<Deps, Rest> extends [never] ? [] : MapDeps<Deps, Rest>),
]
: [never];