@triviality/core
Version:
Purely typed service container
97 lines (87 loc) • 3.52 kB
text/typescript
/**
* Context for creating new service factories.
*/
import { ServiceFactory, ServiceTag, SF } from '../ServiceFactory';
import { once } from '../lib';
import { ServiceFunctionReferenceContainer } from '../Container/ServiceFunctionReferenceContainer';
import { TaggedServiceFactoryReference } from '../Value/TaggedServiceFactoryReference';
import { getCurrentContainer, getCurrentFeatureFactory, inCurrentContext } from './GlobalContext';
import { InternalServiceFactoryReference } from '../Value/InternalServiceFactoryReference';
import { ServiceFunctionReferenceContainerInterface } from '../Container/ServiceFunctionReferenceContainerInterface';
import { MergedTaggedServiceFunctionReference } from '../Value/MergedTaggedServiceFunctionReference';
export interface ReferenceContext {
/**
* Makes a service reference so they can be overridden or decorated when passed as return value
* to the service factory.
*
* @example
* const sf = ({reference}) => {
* const someService = reference(() => 'hi!');
* return {someService, otherService: () => someService() }
* }
*
* Now someService can be overridden, the service in otherService also overridden.
*/
reference<S>(sf: ServiceFactory<S>): ServiceFactory<S>;
}
export const SERVICE_REFERENCE = Symbol.for('SERVICE_REFERENCE');
export const asReference = <T>(sf: SF<T>): SF<T> => {
let factory: SF<T> = once(sf);
const result: any = () => factory();
/**
* Only used when services not called by container factory.
*/
if (!inCurrentContext()) {
return result;
}
const currentContainer = getCurrentContainer();
/**
* Multiple dependency can reference this internal dependency.
*/
const referencedDependency = new InternalServiceFactoryReference({
factory: sf,
feature: getCurrentFeatureFactory(),
});
factory = currentContainer.add(referencedDependency);
/**
* When the service factory is returned by a SF, this function will be called.
*/
result[SERVICE_REFERENCE] = (builder: ServiceFunctionReferenceContainer, tag: ServiceTag) => {
const dependency = new TaggedServiceFactoryReference({
tag,
factory,
feature: getCurrentFeatureFactory(),
});
referencedDependency.addReferenceProxy(dependency);
factory = builder.add(dependency);
};
return result;
};
export const asMergeReference = <T>(reference: () => TaggedServiceFactoryReference): SF<T> => {
const result: any = () => {
return reference().getProxy()();
};
result[SERVICE_REFERENCE] = (builder: ServiceFunctionReferenceContainer, tag: ServiceTag) => {
const dependency = new MergedTaggedServiceFunctionReference(
reference(),
tag,
getCurrentFeatureFactory(),
);
/**
* Does this re-assignment have any use?
*/
builder.add(dependency);
};
return result;
};
export const isServiceReferenced = (factory: any | SF<any>) => {
return typeof factory[SERVICE_REFERENCE] === 'function';
};
export const handleServiceReferenced = <T>(builder: ServiceFunctionReferenceContainerInterface, factory: SF<T>, tag: ServiceTag): SF<T> => {
return (factory as any)[SERVICE_REFERENCE](builder, tag);
};
export const wrapReturnAsReference: <T extends ((...args: any[]) => ServiceFactory<any>)>(toWrap: T) => T =
((toWrap: any) => (...args: any[]): ServiceFactory<any> => asReference(toWrap(...args))) as any;
export const createServiceFactoryReferenceContext = (): ReferenceContext => ({
reference: asReference,
});