diff --git a/packages/microservice/package.json b/packages/microservice/package.json new file mode 100644 index 0000000..11e2b0a --- /dev/null +++ b/packages/microservice/package.json @@ -0,0 +1,16 @@ +{ + "name": "@autoplex/microservice", + "version": "0.0.0", + "main": "dist/lib/index.js", + "types": "dist/typings/index.d.ts", + "license": "MIT", + "scripts": { + "build": "yarn run clean && tsc", + "clean": "rimraf ./dist" + }, + "devDependencies": { + "@types/node": "^15.0.1", + "rimraf": "^3.0.2", + "typescript": "^4.2.4" + } +} diff --git a/packages/microservice/src/InternalService.ts b/packages/microservice/src/InternalService.ts new file mode 100644 index 0000000..07ba3cc --- /dev/null +++ b/packages/microservice/src/InternalService.ts @@ -0,0 +1,60 @@ +import { Microservice } from "./Microservice"; + +/** + * A generic service + */ +export abstract class InternalService +{ + /** + * The application instance + */ + protected readonly app: T; + + /** + * Enable/disable logging for this service + */ + public logging: boolean = true; + + /** + * Create a new service + */ + public constructor(app: T) { + this.app = app; + } + + // Required Service Implementation ------------------------------------------------------------- + + /** + * The service name + */ + public abstract get name(): string; + + /** + * Boot the service + */ + public abstract boot(): Promise; + + /** + * Shut the application down + */ + public abstract shutdown(): Promise; + + // Miscellaneous ------------------------------------------------------------------------------ + + /** + * Indicate the application is ready + */ + public start() { + // no-op + }; + + /** + * Service-specific logging + */ + public log(...args: any[]) { + if (!this.logging) { + return; + } + console.log(`[${this.name}]:`, ...args); + } +} diff --git a/packages/microservice/src/Microservice.ts b/packages/microservice/src/Microservice.ts new file mode 100644 index 0000000..6f59d10 --- /dev/null +++ b/packages/microservice/src/Microservice.ts @@ -0,0 +1,122 @@ +import { InternalService } from "./InternalService"; +import assert from "assert"; + +/** + * Application InternalService map + */ +interface InternalServiceMap { + [name: string]: InternalService +} + +/** + * The InternalService constructor type + */ +type InternalServiceConstructor = new (app: T) => InternalService; + +/** + * The main application class + */ +export class Microservice +{ + /** + * Maintain a static reference to the application instance + */ + private static __instance: Microservice; + + /** + * All available InternalServices + */ + protected services: InternalServiceMap = {}; + + /** + * Return the current application instance + */ + public static instance() { + return this.__instance; + } + + /** + * Create a new application instance + */ + public constructor() { + Microservice.__instance = this; + } + + // Overridable -------------------------------------------------------------------------------- + + /** + * Invoked when the application has finished booting + */ + protected onStart(): void|Promise {} + + // Application Management ---------------------------------------------------------------------- + + /** + * Boot the application and all of the InternalServices + */ + protected async boot() { + let InternalServices = Object.values(this.services); + return Promise.all(InternalServices.map(InternalService => InternalService.boot())); + } + + /** + * Shutdown the application + */ + protected shutdown() { + let InternalServices = Object.values(this.services); + return Promise.all(InternalServices.map(InternalService => InternalService.shutdown())); + } + + /** + * Start the application + */ + public async exec() { + await this.boot(); + await this.onStart(); + for (let InternalService of Object.values(this.services)) { + InternalService.start(); + } + } + + /** + * Quit the application + */ + public async quit(code: number = 0) { + await this.shutdown(); + process.exit(code); + } + + // InternalService Management -------------------------------------------------------------------------- + + /** + * Install InternalServices into the application + */ + protected installServices(this: T, InternalServices: InternalServiceConstructor[]) { + for (let InternalServiceClass of InternalServices) { + this.installService(InternalServiceClass); + } + } + + /** + * Install a InternalService into the application + */ + protected installService(this: T, InternalServiceClass: InternalServiceConstructor) { + let InternalService = new InternalServiceClass(this); + this.services[InternalService.name] = InternalService; + } + + /** + * Get all available InternalServices + */ + public serviceList() { + return Object.keys(this.services); + } + + /** + * Get an application InternalService instance + */ + public service>(InternalServiceName: string) { + assert(InternalServiceName in this.services); + return this.services[InternalServiceName]; + } +} diff --git a/packages/microservice/src/index.ts b/packages/microservice/src/index.ts new file mode 100644 index 0000000..91c2b81 --- /dev/null +++ b/packages/microservice/src/index.ts @@ -0,0 +1,2 @@ +export * from "./Microservice"; +export * from "./InternalService"; diff --git a/packages/microservice/tsconfig.json b/packages/microservice/tsconfig.json new file mode 100644 index 0000000..d0c49bc --- /dev/null +++ b/packages/microservice/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.package.json", + "compilerOptions": { + "outDir": "dist/lib", + "declarationDir": "dist/typings" + } +} diff --git a/packages/microservice/yarn.lock b/packages/microservice/yarn.lock new file mode 100644 index 0000000..f175550 --- /dev/null +++ b/packages/microservice/yarn.lock @@ -0,0 +1,92 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@^15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.1.tgz#ef34dea0881028d11398be5bf4e856743e3dc35a" + integrity sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +typescript@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=