refactor: cleanup (#348)

* some wip code

Co-authored-by: Jacob Nguyen <jacoobes@users.noreply.github.com>

* general idea

* style

* making shrimple truly optional

* got optional localizer working

* proposing api notation?

* prepare for localization map

* add localsFor

* merge some internals

* boss call

* add test for init functionality

* add documentation

* inline and cleanup

* feat: logging for experimental json loading

* loosen typings

* dev workflow and cleaning up comments

* cleaning up a bit more

* rename Localizer -> Localization

* more documentation, change dir for default localizer

* some tests

* "

* move stuff, refactor, deprecate

* yarnb

* Update index.ts

---------

Co-authored-by: Jacob Nguyen <jacoobes@users.noreply.github.com>
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
Co-authored-by: jacob <jacoobes@sern.dev>
This commit is contained in:
2024-02-10 00:46:16 +01:00
committed by GitHub
parent 5cad432589
commit 45cbda7b42
36 changed files with 311 additions and 272 deletions

View File

@@ -1,11 +1,12 @@
import * as assert from 'assert';
import { composeRoot, useContainer } from './dependency-injection';
import type { DependencyConfiguration } from '../../types/ioc';
import { useContainer } from './dependency-injection';
import type { CoreDependencies, DependencyConfiguration } from '../../types/ioc';
import { CoreContainer } from './container';
import { Result } from 'ts-results-es'
import { Result } from 'ts-results-es';
import { DefaultServices } from '../_internal';
import { AnyFunction } from '../../types/utility';
import type { Logging } from '../contracts/logging';
//SIDE EFFECT: GLOBAL DI
let containerSubject: CoreContainer<Partial<Dependencies>>;
@@ -29,19 +30,18 @@ export function disposeAll(logger: Logging|undefined) {
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
}
const dependencyBuilder = (container: any, excluded: string[]) => {
const dependencyBuilder = (container: any, excluded: string[] ) => {
type Insertable =
| ((container: CoreContainer<Dependencies>) => unknown )
| Record<PropertyKey, unknown>
| object
return {
/**
* Insert a dependency into your container.
* Supply the correct key and dependency
*/
add(key: keyof Dependencies, v: Insertable) {
Result
.wrap(() => container.add({ [key]: v}))
.expect("Failed to add " + key);
Result.wrap(() => container.add({ [key]: v}))
.expect("Failed to add " + key);
},
/**
* Exclude any dependencies from being added.
@@ -50,15 +50,15 @@ const dependencyBuilder = (container: any, excluded: string[]) => {
exclude(...keys: (keyof Dependencies)[]) {
keys.forEach(key => excluded.push(key));
},
/**
* @param key the key of the dependency
* @param v The dependency to swap out.
* Swap out a preexisting dependency.
*/
swap(key: keyof Dependencies, v: Insertable) {
Result
.wrap(() => container.upsert({ [key]: v }))
.expect("Failed to update " + key);
Result.wrap(() => container.upsert({ [key]: v }))
.expect("Failed to update " + key);
},
/**
* @param key the key of the dependency
@@ -70,9 +70,8 @@ const dependencyBuilder = (container: any, excluded: string[]) => {
* Swap out a preexisting dependency.
*/
addDisposer(key: keyof Dependencies, cleanup: AnyFunction) {
Result
.wrap(() => container.addDisposer({ [key] : cleanup }))
.expect("Failed to addDisposer for" + key);
Result.wrap(() => container.addDisposer({ [key] : cleanup }))
.expect("Failed to addDisposer for" + key);
}
};
};
@@ -87,15 +86,45 @@ export const insertLogger = (containerSubject: CoreContainer<any>) => {
containerSubject
.upsert({'@sern/logger': () => new DefaultServices.DefaultLogging});
}
/**
* Given the user's conf, check for any excluded/included 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
*/
function composeRoot(
container: CoreContainer<Partial<Dependencies>>,
conf: DependencyConfiguration,
) {
//container should have no client or logger yet.
const hasLogger = conf.exclude?.has('@sern/logger');
if (!hasLogger) {
insertLogger(container);
}
//Build the container based on the callback provided by the user
conf.build(container as CoreContainer<Omit<CoreDependencies, '@sern/client'>>);
if (!hasLogger) {
container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
}
container.ready();
}
export async function makeDependencies<const T extends Dependencies>
(conf: ValidDependencyConfig) {
containerSubject = new CoreContainer();
if(typeof conf === 'function') {
const excluded: string[] = [];
conf(dependencyBuilder(containerSubject, excluded));
const includeLogger =
!excluded.includes('@sern/logger')
&& !containerSubject.getTokens()['@sern/logger'];
if(!excluded.includes('@sern/logger')
&& !containerSubject.getTokens()['@sern/logger']) {
if(includeLogger) {
insertLogger(containerSubject);
}
@@ -107,5 +136,3 @@ export async function makeDependencies<const T extends Dependencies>
return useContainer<T>();
}

View File

@@ -1,9 +1,10 @@
import { Container } from 'iti';
import { Disposable, SernEmitter } from '../';
import { Disposable } from '../';
import * as assert from 'node:assert';
import { Subject } from 'rxjs';
import { DefaultServices, ModuleStore } from '../_internal';
import * as Hooks from './hooks'
import * as Hooks from './hooks';
import { EventEmitter } from 'node:events';
/**
@@ -17,12 +18,13 @@ export class CoreContainer<T extends Partial<Dependencies>> extends Container<T,
assert.ok(!this.isReady(), 'Listening for dispose & init should occur prior to sern being ready.');
const { unsubscribe } = Hooks.createInitListener(this);
this.ready$
.subscribe({ complete: unsubscribe });
(this as Container<{}, {}>)
.add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling(),
'@sern/emitter': () => new SernEmitter,
.add({ '@sern/errors': () => new DefaultServices.DefaultErrorHandling,
'@sern/emitter': () => new EventEmitter({ captureRejections: true }),
'@sern/store': () => new ModuleStore })
.add(ctx => {
return { '@sern/modules': () =>
@@ -33,19 +35,25 @@ export class CoreContainer<T extends Partial<Dependencies>> extends Container<T,
isReady() {
return this.ready$.closed;
}
hasKey(key: string): boolean {
return Boolean((this as Container<any,any>)._context[key]);
}
override async disposeAll() {
const otherDisposables = Object
.entries(this._context)
.flatMap(([key, value]) =>
'dispose' in value ? [key] : []);
for(const key of otherDisposables) {
otherDisposables.forEach(key => {
//possible source of bug: dispose is a property.
this.addDisposer({ [key]: (dep: Disposable) => dep.dispose() } as never);
}
await super.disposeAll()
})
await super.disposeAll();
}
ready() {
this.ready$.complete();
this.ready$.unsubscribe();

View File

@@ -1,6 +1,6 @@
import type { CoreDependencies, DependencyConfiguration, IntoDependencies } from '../../types/ioc';
import { insertLogger, useContainerRaw } from './base';
import { CoreContainer } from './container';
import assert from 'node:assert';
import type { IntoDependencies } from '../../types/ioc';
import { useContainerRaw } from './base';
/**
* @__PURE__
@@ -25,6 +25,7 @@ export function transient<T>(cb: () => () => T) {
* The new Service api, a cleaner alternative to useContainer
* To obtain intellisense, ensure a .d.ts file exists in the root of compilation.
* Usually our scaffolding tool takes care of this.
* Note: this method only works AFTER your container has been initiated
* @since 3.0.0
* @example
* ```ts
@@ -34,7 +35,9 @@ export function transient<T>(cb: () => () => T) {
*
*/
export function Service<const T extends keyof Dependencies>(key: T) {
return useContainerRaw().get(key)!;
const dep = useContainerRaw().get(key)!;
assert(dep, "Requested key " + key + " returned undefined");
return dep;
}
/**
* @since 3.0.0
@@ -46,32 +49,9 @@ export function Services<const T extends (keyof Dependencies)[]>(...keys: [...T]
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 function composeRoot(
container: CoreContainer<Partial<Dependencies>>,
conf: DependencyConfiguration,
) {
//container should have no client or logger yet.
const hasLogger = conf.exclude?.has('@sern/logger');
if (!hasLogger) {
insertLogger(container);
}
//Build the container based on the callback provided by the user
conf.build(container as CoreContainer<Omit<CoreDependencies, '@sern/client'>>);
if (!hasLogger) {
container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
}
container.ready();
}
export function useContainer<const T extends Dependencies>() {
return <V extends (keyof T)[]>(...keys: [...V]) =>
keys.map(key => useContainerRaw().get(key as keyof Dependencies)) as IntoDependencies<V>;
}

View File

@@ -9,7 +9,7 @@ type HookName = 'init';
export const createInitListener = (coreContainer : CoreContainer<any>) => {
const initCalled = new Set<PropertyKey>();
const hasCallableMethod = createPredicate(initCalled);
const unsubscribe = coreContainer.on('containerUpserted', async (event) => {
const unsubscribe = coreContainer.on('containerUpserted', async event => {
if(isNotHookable(event)) {
return;
@@ -21,6 +21,7 @@ export const createInitListener = (coreContainer : CoreContainer<any>) => {
}
});
return { unsubscribe };
}