@triviality/core
Version:
Purely typed service container
153 lines (132 loc) • 4.21 kB
text/typescript
import { assetServiceFactory, ServiceFactory, ServiceTag, SF } from '../ServiceFactory';
import { ImmutableServiceReferenceList } from './ImmutableServiceReferenceList';
import { FeatureFactory, FF } from '../FeatureFactory';
import { ServiceFactoryReference } from './ServiceFactoryReference';
import { AsyncServiceFunctionReferenceError } from './AsyncServiceFunctionReferenceError';
export interface ServiceFactoryReferenceOptions {
readonly factory: SF;
/**
* The feature that created this service function.
*/
readonly feature: FF;
}
export interface ServiceFunctionReferenceConfiguration {
/**
* Unique identify this dependency with. This should
* always be the same, should not be effected by async operations.
*/
readonly uuid: string;
/**
* The callback function proxy this service is referenced by.
*
* This allows that the actual service can be overriden.
*
* Dependency -> Service factory Proxy -> Actual service factory.
*/
readonly proxy: ServiceFactory<any>;
}
/**
* Purpose is to hold information about a service function connection.
*
* TODO: Convert mayhem/inheritance, state machine classes.
* The ideal world there is only 1 service factory reference for each service, now there are multiple.
*/
export abstract class BaseServiceFactoryReference {
/**
* All the dependencies this services is depended on.
*/
protected dependencies: ImmutableServiceReferenceList;
/**
* The actual service function reference.
*/
protected readonly factory: SF;
/**
* If the original service is invoked.
*/
protected serviceFactoryInvoked: boolean = false;
/**
* If the service factory created instance.
*/
protected serviceDefined: boolean = false;
/**
* The instance created by the service factory.
*/
protected service: unknown | undefined = undefined;
/**
* The feature that created this service function.
*/
protected readonly feature: FeatureFactory<any>;
protected _configuration: ServiceFunctionReferenceConfiguration | null = null;
constructor({ factory, feature }: ServiceFactoryReferenceOptions) {
assetServiceFactory(factory);
this.factory = factory;
this.feature = feature;
this.dependencies = new ImmutableServiceReferenceList();
}
public get configuration() {
if (this._configuration === null) {
throw new Error('Service reference not yet configured.');
}
return this._configuration;
}
public getUuid(): string {
return this.configuration.uuid;
}
public callServiceFactory(thisReference: Record<ServiceTag, SF>) {
this.assertServiceFunctionNotYetInvoked();
this.serviceFactoryInvoked = true;
try {
this.service = this.factory.bind(thisReference)();
} catch (e) {
if (e instanceof AsyncServiceFunctionReferenceError) {
this.serviceFactoryInvoked = false;
}
throw e;
}
this.serviceDefined = true;
}
public getService(): unknown {
this.assertHasServiceInstance();
return this.service;
}
public setContainerConfiguration(configuration: ServiceFunctionReferenceConfiguration) {
this.assertNotYetConfigured();
this._configuration = configuration;
}
public getFactory(): SF<any> {
return this.factory;
}
public getFeature(): FF {
return this.feature;
}
public getProxy(): SF<any> {
return this.configuration.proxy;
}
public hasServiceInstance() {
return this.serviceDefined;
}
public addDependency(dependency: ServiceFactoryReference) {
this.dependencies = this.dependencies.add(dependency);
}
public getDependencies() {
return this.dependencies;
}
public label() {
return `#${this.getUuid()} ${this.feature.name || '?'}`;
}
protected assertServiceFunctionNotYetInvoked() {
if (this.serviceFactoryInvoked) {
throw new Error('Service factory is already invoked.');
}
}
private assertNotYetConfigured() {
if (this._configuration !== null) {
throw new Error('Service reference already configured.');
}
}
private assertHasServiceInstance() {
if (!this.hasServiceInstance()) {
throw new Error('No service instance');
}
}
}