|
|
@ -1,13 +1,15 @@ |
|
|
|
import assert from "assert"; |
|
|
|
import { EventEmitter } from "events"; |
|
|
|
import { MicroserviceState } from "./schema"; |
|
|
|
import { InternalServiceConflictError, InternalServiceNotFoundError } from "./errors"; |
|
|
|
import { InternalService } from "./InternalService"; |
|
|
|
import { ExitCode, MicroserviceState } from "./schema"; |
|
|
|
|
|
|
|
/** |
|
|
|
* The InternalService constructor type |
|
|
|
*/ |
|
|
|
// type InternalServiceConstructor<
|
|
|
|
// T extends Microservice = Microservice
|
|
|
|
// > = new (microservice: T) => InternalService<T>;
|
|
|
|
type InternalServiceConstructor< |
|
|
|
T extends Microservice = Microservice |
|
|
|
> = new (microservice: T) => InternalService<T>; |
|
|
|
|
|
|
|
/** |
|
|
|
* Declare EventEmitter types |
|
|
@ -43,13 +45,22 @@ export class Microservice extends EventEmitter |
|
|
|
*/ |
|
|
|
#quitHandler?: (value: number | PromiseLike<number>) => void; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* The current state of the microservice |
|
|
|
*/ |
|
|
|
#state: MicroserviceState = MicroserviceState.Idle; |
|
|
|
|
|
|
|
// Microservice Management ---------------------------------------------------------------------
|
|
|
|
/** |
|
|
|
* A map of the installed services |
|
|
|
*/ |
|
|
|
#services = new Map<ThisType<this>, InternalService<Microservice>>(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Indicate if exec has been invoked |
|
|
|
*/ |
|
|
|
#hasStarted: boolean = false; |
|
|
|
|
|
|
|
// Microservice State Procedures ---------------------------------------------------------------
|
|
|
|
|
|
|
|
/** |
|
|
|
* Invoke the boot phase of the microservice |
|
|
@ -58,7 +69,7 @@ export class Microservice extends EventEmitter |
|
|
|
process.on("SIGINT", this.quit.bind(this)); |
|
|
|
this.setState(MicroserviceState.Booting); |
|
|
|
this.emit("boot"); |
|
|
|
return true; |
|
|
|
return await this.dispatch("boot"); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -67,7 +78,7 @@ export class Microservice extends EventEmitter |
|
|
|
protected async start() { |
|
|
|
this.setState(MicroserviceState.Starting); |
|
|
|
this.emit("start"); |
|
|
|
return true; |
|
|
|
return await this.dispatch("start"); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -88,18 +99,41 @@ export class Microservice extends EventEmitter |
|
|
|
process.off("SIGINT", this.quit.bind(this)); |
|
|
|
this.setState(MicroserviceState.Quitting); |
|
|
|
this.emit("shutdown"); |
|
|
|
return await this.dispatch("shutdown"); |
|
|
|
} |
|
|
|
|
|
|
|
// Internal Service Handling -------------------------------------------------------------------
|
|
|
|
|
|
|
|
/** |
|
|
|
* Dispatch an event across all installed services |
|
|
|
*/ |
|
|
|
protected async dispatch<T extends Microservice, K extends keyof InternalService<T>>( |
|
|
|
this: T, method: K, ...args: Parameters<InternalService<T>[K]>) |
|
|
|
{ |
|
|
|
let services = <InternalService<T>[]> Array.from(this.services().values()); |
|
|
|
try { |
|
|
|
await Promise.all(services.map(service => (<any>service[method]).apply(service, args))); |
|
|
|
} catch(e) { |
|
|
|
console.error(e); |
|
|
|
return false; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// Microservice Interface ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
/** |
|
|
|
* Run the application |
|
|
|
*/ |
|
|
|
public async exec() { |
|
|
|
public async exec(): Promise<number> { |
|
|
|
// Exit if not in an idling state
|
|
|
|
if (this.state() != MicroserviceState.Idle) { |
|
|
|
console.error("Cannot exec an already-started microservice"); |
|
|
|
return 1; |
|
|
|
if (this.#hasStarted) { |
|
|
|
throw Error("Cannot exec an already-started microservice"); |
|
|
|
} |
|
|
|
|
|
|
|
// Indicate that the microservice has started
|
|
|
|
this.#hasStarted = true; |
|
|
|
|
|
|
|
// Run the microservice application
|
|
|
|
let exitCode = await (async () => { |
|
|
|
// Create the microservice execution promise to listen for quit events
|
|
|
@ -112,12 +146,12 @@ export class Microservice extends EventEmitter |
|
|
|
// Boot the microservice and internal services
|
|
|
|
if (!await this.boot()) { // no need to check for hasQuit
|
|
|
|
console.error("Failed to boot the microservice"); |
|
|
|
return 1; |
|
|
|
return ExitCode.BootError; |
|
|
|
} |
|
|
|
// Start the internal services
|
|
|
|
if (!hasQuit && !await this.start()) { |
|
|
|
console.error("Failed to start the microservice"); |
|
|
|
return 2; |
|
|
|
return ExitCode.StartError; |
|
|
|
} |
|
|
|
// If the application has not quit, we can run the app
|
|
|
|
let exitCode: number; |
|
|
@ -130,7 +164,7 @@ export class Microservice extends EventEmitter |
|
|
|
// Shutdown the microservice
|
|
|
|
if (!await this.shutdown()) { |
|
|
|
console.error("Failed to shutdown the microservice"); |
|
|
|
return 3; |
|
|
|
return ExitCode.ShutdownError; |
|
|
|
} |
|
|
|
return exitCode; |
|
|
|
})(); |
|
|
@ -141,7 +175,7 @@ export class Microservice extends EventEmitter |
|
|
|
/** |
|
|
|
* Quit the application |
|
|
|
*/ |
|
|
|
public quit(code: number = 0) { |
|
|
|
public quit(code: number = ExitCode.Ok) { |
|
|
|
if (this.state() == MicroserviceState.Idle) { |
|
|
|
this.setState(MicroserviceState.Finished); |
|
|
|
return; |
|
|
@ -152,8 +186,42 @@ export class Microservice extends EventEmitter |
|
|
|
this.#quitHandler(code); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Install InternalServices into th eapplication |
|
|
|
*/ |
|
|
|
public installServices<T extends Microservice>(this: T, Classes: InternalServiceConstructor[]) { |
|
|
|
Classes.forEach(Class => this.installService(Class)); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Install an InternalService into the application |
|
|
|
*/ |
|
|
|
public installService<T extends Microservice>(this: T, Class: InternalServiceConstructor) { |
|
|
|
if (this.#services.has(Class)) { |
|
|
|
throw new InternalServiceConflictError(Class); |
|
|
|
} |
|
|
|
this.#services.set(Class, new Class(this)); |
|
|
|
} |
|
|
|
|
|
|
|
// Accessors -----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/** |
|
|
|
* Fetch an instance of an installed service |
|
|
|
*/ |
|
|
|
public service<T extends Microservice, U extends InternalServiceConstructor<T>>(this: T, Class: U) { |
|
|
|
if (!this.#services.has(Class)) { |
|
|
|
throw new InternalServiceNotFoundError(Class); |
|
|
|
} |
|
|
|
return <InstanceType<U>>this.#services.get(Class); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the map of installed services |
|
|
|
*/ |
|
|
|
public services() { |
|
|
|
return this.#services; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the current state of the microservice |
|
|
|
*/ |
|
|
|