Initial import
This commit is contained in:
commit
be1f615858
192
.gitignore
vendored
Normal file
192
.gitignore
vendored
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
# ---> JetBrains
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
# ---> Node
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
/lib
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
8
.idea/lib.iml
Normal file
8
.idea/lib.iml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/lib.iml" filepath="$PROJECT_DIR$/.idea/lib.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
112
package-lock.json
generated
Normal file
112
package-lock.json
generated
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"name": "@extollo/lib",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@extollo/di": {
|
||||||
|
"version": "file:../di",
|
||||||
|
"requires": {
|
||||||
|
"@extollo/util": "file:../util",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@extollo/util": {
|
||||||
|
"version": "file:../util",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "^14.14.20",
|
||||||
|
"@types/uuid": "^8.3.0",
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"typescript": "^4.1.3",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "14.14.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
|
||||||
|
"integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw=="
|
||||||
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg=="
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reflect-metadata": {
|
||||||
|
"version": "0.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||||
|
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@extollo/util": {
|
||||||
|
"version": "file:../util",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "^14.14.20",
|
||||||
|
"@types/uuid": "^8.3.0",
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"typescript": "^4.1.3",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "14.14.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
|
||||||
|
"integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw=="
|
||||||
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg=="
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dotenv": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
package.json
Normal file
33
package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@extollo/lib",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "The framework library that lifts up your code.",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@extollo/di": "file:../di",
|
||||||
|
"@extollo/util": "file:../util",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc",
|
||||||
|
"app": "tsc && node lib/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/**/*"
|
||||||
|
],
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"postversion": "git push && git push --tags",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://code.garrettmills.dev/extollo/lib"
|
||||||
|
},
|
||||||
|
"author": "garrettmills <shout@garrettmills.dev>",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
3
src/http/Controller.ts
Normal file
3
src/http/Controller.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import {AppClass} from "../lifecycle/AppClass";
|
||||||
|
|
||||||
|
export class Controller extends AppClass {}
|
16
src/index.ts
Normal file
16
src/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export * from './service/Logging'
|
||||||
|
|
||||||
|
export * from './lifecycle/RunLevelErrorHandler'
|
||||||
|
export * from './lifecycle/Application'
|
||||||
|
export * from './lifecycle/AppClass'
|
||||||
|
export * from './lifecycle/Unit'
|
||||||
|
|
||||||
|
export * from './http/Controller'
|
||||||
|
|
||||||
|
export * from './service/Canonical'
|
||||||
|
export * from './service/CanonicalInstantiable'
|
||||||
|
export * from './service/CanonicalRecursive'
|
||||||
|
export * from './service/CanonicalStatic'
|
||||||
|
export * from './service/FakeCanonical'
|
||||||
|
export * from './service/Config'
|
||||||
|
export * from './service/Controllers'
|
56
src/lifecycle/AppClass.ts
Normal file
56
src/lifecycle/AppClass.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {Application} from './Application';
|
||||||
|
import {Container} from "@extollo/di";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base type for a class that supports binding methods by string.
|
||||||
|
*/
|
||||||
|
export interface Bindable {
|
||||||
|
getBoundMethod(methodName: string): (...args: any[]) => any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given object is bindable.
|
||||||
|
* @param what
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
export function isBindable(what: any): what is Bindable {
|
||||||
|
return (
|
||||||
|
what
|
||||||
|
&& typeof what.getBoundMethod === 'function'
|
||||||
|
&& what.getBoundMethod.length === 1
|
||||||
|
&& typeof what.getBoundMethod('getBoundMethod') === 'function'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AppClass {
|
||||||
|
private readonly appClassApplication!: Application;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.appClassApplication = Application.getApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected app(): Application {
|
||||||
|
return this.appClassApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected container(): Container {
|
||||||
|
return this.appClassApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the method with the given name from this class, bound to this class.
|
||||||
|
* @param {string} methodName
|
||||||
|
* @return function
|
||||||
|
*/
|
||||||
|
public getBoundMethod(methodName: string): (...args: any[]) => any {
|
||||||
|
// @ts-ignore
|
||||||
|
if ( typeof this[methodName] !== 'function' ) {
|
||||||
|
throw new TypeError(`Attempt to get bound method for non-function type: ${methodName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (...args: any[]): any => {
|
||||||
|
// @ts-ignore
|
||||||
|
return this[methodName](...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
198
src/lifecycle/Application.ts
Normal file
198
src/lifecycle/Application.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import {Container} from '@extollo/di';
|
||||||
|
import {
|
||||||
|
ErrorWithContext,
|
||||||
|
globalRegistry,
|
||||||
|
infer,
|
||||||
|
isLoggingLevel,
|
||||||
|
PathLike,
|
||||||
|
StandardLogger,
|
||||||
|
universalPath,
|
||||||
|
UniversalPath
|
||||||
|
} from '@extollo/util';
|
||||||
|
|
||||||
|
import {Logging} from '../service/Logging';
|
||||||
|
import {RunLevelErrorHandler} from "./RunLevelErrorHandler";
|
||||||
|
import {Unit, UnitStatus} from "./Unit";
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
export function env(key: string, defaultValue?: any): any {
|
||||||
|
return Application.getApplication().env(key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Application extends Container {
|
||||||
|
public static getContainer(): Container {
|
||||||
|
const existing = <Container | undefined> globalRegistry.getGlobal('extollo/injector')
|
||||||
|
if ( !existing ) {
|
||||||
|
const container = new Application()
|
||||||
|
globalRegistry.setGlobal('extollo/injector', container)
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing as Container
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getApplication(): Application {
|
||||||
|
const existing = <Container | undefined> globalRegistry.getGlobal('extollo/injector')
|
||||||
|
if ( existing && !(existing instanceof Application) ) {
|
||||||
|
const app = new Application()
|
||||||
|
existing.cloneTo(app)
|
||||||
|
|
||||||
|
globalRegistry.setGlobal('extollo/injector', app)
|
||||||
|
return app
|
||||||
|
} else if ( !existing ) {
|
||||||
|
const app = new Application()
|
||||||
|
globalRegistry.setGlobal('extollo/injector', app)
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
protected baseDir!: string
|
||||||
|
protected basePath!: UniversalPath
|
||||||
|
protected applicationUnits: (typeof Unit)[] = []
|
||||||
|
protected instantiatedUnits: Unit[] = []
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
if ( !this.hasKey(Application) ) {
|
||||||
|
this.register(Application)
|
||||||
|
this.instances.push({
|
||||||
|
key: Application,
|
||||||
|
value: this,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !this.hasKey('app') ) {
|
||||||
|
this.registerSingleton('app', this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get root() {
|
||||||
|
return this.basePath.concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
get appRoot() {
|
||||||
|
return this.basePath.concat('app')
|
||||||
|
}
|
||||||
|
|
||||||
|
path(...parts: PathLike[]) {
|
||||||
|
return this.basePath.concat(...parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
appPath(...parts: PathLike[]) {
|
||||||
|
return this.basePath.concat('app', ...parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorHandler() {
|
||||||
|
const rleh: RunLevelErrorHandler = this.make<RunLevelErrorHandler>(RunLevelErrorHandler)
|
||||||
|
return rleh.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
errorWrapContext(e: Error, context: {[key: string]: any}): ErrorWithContext {
|
||||||
|
const rleh: RunLevelErrorHandler = this.make<RunLevelErrorHandler>(RunLevelErrorHandler)
|
||||||
|
return rleh.wrapContext(e, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
scaffold(absolutePathToApplicationRoot: string, applicationUnits: (typeof Unit)[]) {
|
||||||
|
this.baseDir = absolutePathToApplicationRoot
|
||||||
|
this.basePath = universalPath(absolutePathToApplicationRoot)
|
||||||
|
this.applicationUnits = applicationUnits
|
||||||
|
|
||||||
|
this.bootstrapEnvironment()
|
||||||
|
this.setupLogging()
|
||||||
|
|
||||||
|
this.make<Logging>(Logging).debug(`Application root: ${this.baseDir}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setupLogging() {
|
||||||
|
const standard: StandardLogger = this.make<StandardLogger>(StandardLogger)
|
||||||
|
const logging: Logging = this.make<Logging>(Logging)
|
||||||
|
|
||||||
|
logging.registerLogger(standard)
|
||||||
|
|
||||||
|
try {
|
||||||
|
logging.verbose('Attempting to load logging level from the environment...')
|
||||||
|
const envLevel = this.env('EXTOLLO_LOGGING_LEVEL')
|
||||||
|
logging.verbose(`Read logging level: ${envLevel}`)
|
||||||
|
|
||||||
|
if ( isLoggingLevel(envLevel) ) {
|
||||||
|
logging.verbose('Logging level is valid.')
|
||||||
|
logging.level = envLevel
|
||||||
|
logging.debug(`Set logging level from environment: ${envLevel}`)
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bootstrapEnvironment() {
|
||||||
|
dotenv.config({
|
||||||
|
path: this.basePath.concat('.env').toLocal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public env(key: string, defaultValue?: any): any {
|
||||||
|
return infer(process.env[key] ?? '') ?? defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
try {
|
||||||
|
await this.up()
|
||||||
|
await this.down()
|
||||||
|
} catch (e) {
|
||||||
|
this.errorHandler(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async up() {
|
||||||
|
const logging: Logging = this.make<Logging>(Logging)
|
||||||
|
|
||||||
|
logging.info('Starting Extollo...', true)
|
||||||
|
for ( const unitClass of this.applicationUnits ) {
|
||||||
|
const unit: Unit = this.make<Unit>(unitClass)
|
||||||
|
this.instantiatedUnits.push(unit)
|
||||||
|
await this.startUnit(unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async down() {
|
||||||
|
const logging: Logging = this.make<Logging>(Logging)
|
||||||
|
|
||||||
|
logging.info('Stopping Extollo...', true)
|
||||||
|
for ( const unit of this.instantiatedUnits ) {
|
||||||
|
if ( !unit ) continue
|
||||||
|
await this.stopUnit(unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async startUnit(unit: Unit) {
|
||||||
|
const logging: Logging = this.make<Logging>(Logging)
|
||||||
|
|
||||||
|
try {
|
||||||
|
logging.debug(`Starting ${unit.constructor.name}...`)
|
||||||
|
unit.status = UnitStatus.Starting
|
||||||
|
await unit.up()
|
||||||
|
unit.status = UnitStatus.Started
|
||||||
|
logging.info(`Started ${unit.constructor.name}.`)
|
||||||
|
} catch (e) {
|
||||||
|
unit.status = UnitStatus.Error
|
||||||
|
console.log(e)
|
||||||
|
throw this.errorWrapContext(e, {unit_name: unit.constructor.name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async stopUnit(unit: Unit) {
|
||||||
|
const logging: Logging = this.make<Logging>(Logging)
|
||||||
|
|
||||||
|
try {
|
||||||
|
logging.debug(`Stopping ${unit.constructor.name}...`)
|
||||||
|
unit.status = UnitStatus.Stopping
|
||||||
|
await unit.down()
|
||||||
|
unit.status = UnitStatus.Stopped
|
||||||
|
logging.info(`Stopped ${unit.constructor.name}.`)
|
||||||
|
} catch (e) {
|
||||||
|
unit.status = UnitStatus.Error
|
||||||
|
throw this.errorWrapContext(e, {unit_name: unit.constructor.name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/lifecycle/RunLevelErrorHandler.ts
Normal file
74
src/lifecycle/RunLevelErrorHandler.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import * as color from 'colors/safe'
|
||||||
|
import {Logging} from "../service/Logging";
|
||||||
|
import {Inject} from "@extollo/di";
|
||||||
|
import {ErrorWithContext} from "@extollo/util";
|
||||||
|
|
||||||
|
export class RunLevelErrorHandler {
|
||||||
|
@Inject()
|
||||||
|
protected logging!: Logging
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error handler function.
|
||||||
|
* @type (e: Error) => void
|
||||||
|
*/
|
||||||
|
get handle(): (e: Error) => void {
|
||||||
|
return (e: Error) => {
|
||||||
|
this.display(e)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapContext(e: Error, context: {[key: string]: any}): ErrorWithContext {
|
||||||
|
if ( e instanceof ErrorWithContext ) {
|
||||||
|
e.context = {...e.context, ...context}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = new ErrorWithContext(e.message)
|
||||||
|
error.originalError = e
|
||||||
|
error.context = context
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the error to the logger.
|
||||||
|
* @param {Error} e
|
||||||
|
*/
|
||||||
|
display(e: Error) {
|
||||||
|
let operativeError = e
|
||||||
|
let context: {[key: string]: string} = {}
|
||||||
|
if ( e instanceof ErrorWithContext ) {
|
||||||
|
if ( e.originalError ) operativeError = e.originalError
|
||||||
|
context = e.context
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextDisplay = Object.keys(context).map(key => ` - ${key}: ${context[key]}`).join('\n')
|
||||||
|
|
||||||
|
try {
|
||||||
|
let errorString = `RunLevelErrorHandler invoked:
|
||||||
|
|
||||||
|
${color.bgRed(' ')}
|
||||||
|
${color.bgRed(' UNCAUGHT RUN-LEVEL ERROR ')}
|
||||||
|
${color.bgRed(' ')}
|
||||||
|
|
||||||
|
${e.constructor ? e.constructor.name : e.name}
|
||||||
|
${color.red(`---------------------------------------------------`)}
|
||||||
|
${e.stack}
|
||||||
|
`
|
||||||
|
|
||||||
|
if ( contextDisplay ) {
|
||||||
|
errorString += `
|
||||||
|
With the following context:
|
||||||
|
${contextDisplay}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logging.error(errorString, true)
|
||||||
|
} catch (display_e) {
|
||||||
|
// The error display encountered an error...
|
||||||
|
// just throw the original so it makes it out
|
||||||
|
console.error('RunLevelErrorHandler encountered an error:', display_e.message)
|
||||||
|
throw operativeError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/lifecycle/Unit.ts
Normal file
15
src/lifecycle/Unit.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {AppClass} from './AppClass';
|
||||||
|
|
||||||
|
export enum UnitStatus {
|
||||||
|
Starting,
|
||||||
|
Started,
|
||||||
|
Stopping,
|
||||||
|
Stopped,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Unit extends AppClass {
|
||||||
|
public status: UnitStatus = UnitStatus.Stopped
|
||||||
|
public up(): Promise<void> | void {}
|
||||||
|
public down(): Promise<void> | void {}
|
||||||
|
}
|
56
src/service/Canon.ts
Normal file
56
src/service/Canon.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {Canonical} from "./Canonical";
|
||||||
|
import {Singleton} from "@extollo/di";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error throw when a duplicate canonical key is registered.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
|
export class DuplicateResolverKeyError extends Error {
|
||||||
|
constructor(key: string) {
|
||||||
|
super(`There is already a canonical unit with the scope ${key} registered.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error throw when a key that isn't registered with the service.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
|
export class NoSuchCanonicalResolverKeyError extends Error {
|
||||||
|
constructor(key: string) {
|
||||||
|
super(`There is no such canonical unit with the scope ${key} registered.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service used to access various canonical resources.
|
||||||
|
*/
|
||||||
|
@Singleton()
|
||||||
|
export class Canon {
|
||||||
|
/**
|
||||||
|
* The resources registered with this service. Map of canonical service name
|
||||||
|
* to canonical service instance.
|
||||||
|
* @type object
|
||||||
|
*/
|
||||||
|
protected resources: { [key: string]: Canonical<any> } = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a canonical resource by its name key.
|
||||||
|
* @param {string} key
|
||||||
|
* @return Canonical
|
||||||
|
*/
|
||||||
|
resource<T>(key: string): Canonical<T> {
|
||||||
|
if ( !this.resources[key] ) throw new NoSuchCanonicalResolverKeyError(key)
|
||||||
|
return this.resources[key] as Canonical<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a canonical resource.
|
||||||
|
* @param {Canonical} unit
|
||||||
|
*/
|
||||||
|
registerCanonical(unit: Canonical<any>) {
|
||||||
|
const key = unit.canonicalItems
|
||||||
|
if ( this.resources[key] ) throw new DuplicateResolverKeyError(key)
|
||||||
|
this.resources[key] = unit
|
||||||
|
}
|
||||||
|
}
|
125
src/service/Canonical.ts
Normal file
125
src/service/Canonical.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* Base type for a canonical definition.
|
||||||
|
*/
|
||||||
|
import {Canon} from "./Canon";
|
||||||
|
import {universalPath, UniversalPath} from "@extollo/util";
|
||||||
|
import {Logging} from "./Logging";
|
||||||
|
import {Inject} from "@extollo/di";
|
||||||
|
import * as nodePath from 'path'
|
||||||
|
import {Unit} from "../lifecycle/Unit";
|
||||||
|
|
||||||
|
export interface CanonicalDefinition {
|
||||||
|
canonicalName: string,
|
||||||
|
originalName: string,
|
||||||
|
imported: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base type for a canonical name reference.
|
||||||
|
*/
|
||||||
|
export interface CanonicalReference {
|
||||||
|
resource?: string,
|
||||||
|
item: string,
|
||||||
|
particular?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Canonical<T> extends Unit {
|
||||||
|
@Inject()
|
||||||
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly canon!: Canon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base path directory where the canonical definitions reside.
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected appPath: string[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file suffix of files in the base path that should be loaded.
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected suffix: string = '.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singular, programmatic name of one of these canonical items.
|
||||||
|
* @example middleware
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected canonicalItem: string = ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object mapping canonical names to loaded file references.
|
||||||
|
* @type object
|
||||||
|
*/
|
||||||
|
protected loadedItems: { [key: string]: T } = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a canonical reference from its string form to a CanonicalReference.
|
||||||
|
* @param {string} reference
|
||||||
|
* @return CanonicalReference
|
||||||
|
*/
|
||||||
|
public static resolve(reference: string): CanonicalReference {
|
||||||
|
const rscParts = reference.split('::')
|
||||||
|
const resource = rscParts.length > 1 ? rscParts[0] + 's' : undefined
|
||||||
|
const rsc_less = rscParts.length > 1 ? rscParts[1] : rscParts[0]
|
||||||
|
const prtParts = rsc_less.split('.')
|
||||||
|
const item = prtParts[0]
|
||||||
|
const particular = prtParts.length > 1 ? prtParts.slice(1).join('.') : undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
resource, item, particular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public all(): string[] {
|
||||||
|
return Object.keys(this.loadedItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get path(): UniversalPath {
|
||||||
|
return this.app().appPath(...this.appPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get canonicalItems() {
|
||||||
|
return `${this.canonicalItem}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(key: string): T | undefined {
|
||||||
|
return this.loadedItems[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
for await ( const entry of this.path.walk() ) {
|
||||||
|
if ( !entry.endsWith(this.suffix) ) {
|
||||||
|
this.logging.debug(`Skipping file with invalid suffix: ${entry}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const definition = await this.buildCanonicalDefinition(entry)
|
||||||
|
this.logging.verbose(`Registering canonical ${this.canonicalItem} "${definition.canonicalName}" from ${entry}`)
|
||||||
|
this.loadedItems[definition.canonicalName] = await this.initCanonicalItem(definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canon.registerCanonical(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async initCanonicalItem(definition: CanonicalDefinition): Promise<T> {
|
||||||
|
return definition.imported.default
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async buildCanonicalDefinition(filePath: string): Promise<CanonicalDefinition> {
|
||||||
|
const originalName = filePath.replace(this.path.toLocal, '').substr(1)
|
||||||
|
const pathRegex = new RegExp(nodePath.sep, 'g')
|
||||||
|
const canonicalName = originalName.replace(pathRegex, ':')
|
||||||
|
.split('').reverse().join('')
|
||||||
|
.substr(this.suffix.length)
|
||||||
|
.split('').reverse().join('')
|
||||||
|
|
||||||
|
const fullUniversalPath = universalPath(filePath)
|
||||||
|
this.logging.verbose(`Importing from: ${fullUniversalPath}`)
|
||||||
|
|
||||||
|
const imported = await import(fullUniversalPath.toLocal)
|
||||||
|
return { canonicalName, originalName, imported }
|
||||||
|
}
|
||||||
|
}
|
22
src/service/CanonicalInstantiable.ts
Normal file
22
src/service/CanonicalInstantiable.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Error thrown when the item returned from a canonical definition file is not the expected item.
|
||||||
|
* @extends Error
|
||||||
|
*/
|
||||||
|
import {Canonical, CanonicalDefinition} from "./Canonical";
|
||||||
|
import {Instantiable, isInstantiable} from "@extollo/di";
|
||||||
|
|
||||||
|
export class InvalidCanonicalExportError extends Error {
|
||||||
|
constructor(name: string) {
|
||||||
|
super(`Unable to import canonical item from "${name}". The default export of this file is invalid.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CanonicalInstantiable<T> extends Canonical<Instantiable<T>> {
|
||||||
|
public async initCanonicalItem(definition: CanonicalDefinition): Promise<Instantiable<T>> {
|
||||||
|
if ( isInstantiable(definition.imported.default) ) {
|
||||||
|
return this.app().make(definition.imported.default)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidCanonicalExportError(definition.originalName)
|
||||||
|
}
|
||||||
|
}
|
12
src/service/CanonicalRecursive.ts
Normal file
12
src/service/CanonicalRecursive.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import {Canonical} from "./Canonical";
|
||||||
|
|
||||||
|
export class CanonicalRecursive extends Canonical<any> {
|
||||||
|
public get(key: string, fallback?: any): any | undefined {
|
||||||
|
const parts = key.split('.')
|
||||||
|
let currentValue = this.loadedItems
|
||||||
|
for ( const part of parts ) {
|
||||||
|
currentValue = currentValue?.[part]
|
||||||
|
}
|
||||||
|
return currentValue ?? fallback
|
||||||
|
}
|
||||||
|
}
|
13
src/service/CanonicalStatic.ts
Normal file
13
src/service/CanonicalStatic.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {Canonical, CanonicalDefinition} from "./Canonical";
|
||||||
|
import {isStaticClass, StaticClass} from "@extollo/di";
|
||||||
|
import {InvalidCanonicalExportError} from "./CanonicalInstantiable";
|
||||||
|
|
||||||
|
export class CanonicalStatic<T, T2> extends Canonical<StaticClass<T, T2>> {
|
||||||
|
public async initCanonicalItem(definition: CanonicalDefinition): Promise<StaticClass<T, T2>> {
|
||||||
|
if ( isStaticClass(definition.imported.default) ) {
|
||||||
|
return definition.imported.default
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidCanonicalExportError(definition.originalName)
|
||||||
|
}
|
||||||
|
}
|
9
src/service/Config.ts
Normal file
9
src/service/Config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {Singleton} from "@extollo/di";
|
||||||
|
import {CanonicalRecursive} from "./CanonicalRecursive";
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class Config extends CanonicalRecursive {
|
||||||
|
protected appPath: string[] = ['configs']
|
||||||
|
protected suffix: string = '.config.js'
|
||||||
|
protected canonicalItem: string = 'config'
|
||||||
|
}
|
20
src/service/Controllers.ts
Normal file
20
src/service/Controllers.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {CanonicalInstantiable} from "./CanonicalInstantiable";
|
||||||
|
import {Singleton} from "@extollo/di";
|
||||||
|
import {Controller} from "../http/Controller";
|
||||||
|
import {CanonicalDefinition} from "./Canonical";
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class Controllers extends CanonicalInstantiable<Controller> {
|
||||||
|
protected appPath = ['http', 'controllers']
|
||||||
|
protected canonicalItem = 'controller'
|
||||||
|
protected suffix = '.controller.js'
|
||||||
|
|
||||||
|
public async initCanonicalItem(definition: CanonicalDefinition) {
|
||||||
|
const item = await super.initCanonicalItem(definition)
|
||||||
|
if ( !(item instanceof Controller) ) {
|
||||||
|
throw new TypeError(`Invalid controller definition: ${definition.originalName}. Controllers must extend from @extollo/lib.http.Controller.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
7
src/service/FakeCanonical.ts
Normal file
7
src/service/FakeCanonical.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import {Canonical} from "./Canonical";
|
||||||
|
|
||||||
|
export class FakeCanonical<T> extends Canonical<T> {
|
||||||
|
public async up() {
|
||||||
|
this.canon.registerCanonical(this)
|
||||||
|
}
|
||||||
|
}
|
81
src/service/Logging.ts
Normal file
81
src/service/Logging.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {Logger, LoggingLevel, LogMessage} from "@extollo/util";
|
||||||
|
import {Singleton} from "@extollo/di";
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class Logging {
|
||||||
|
protected registeredLoggers: Logger[] = []
|
||||||
|
protected currentLevel: LoggingLevel = LoggingLevel.Warning
|
||||||
|
|
||||||
|
public registerLogger(logger: Logger) {
|
||||||
|
if ( !this.registeredLoggers.includes(logger) ) {
|
||||||
|
this.registeredLoggers.push(logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregisterLogger(logger: Logger) {
|
||||||
|
this.registeredLoggers = this.registeredLoggers.filter(x => x !== logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get level(): LoggingLevel {
|
||||||
|
return this.currentLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
public set level(level: LoggingLevel) {
|
||||||
|
this.currentLevel = level
|
||||||
|
}
|
||||||
|
|
||||||
|
public success(output: any, force = false) {
|
||||||
|
this.writeLog(LoggingLevel.Success, output, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(output: any, force = false) {
|
||||||
|
this.writeLog(LoggingLevel.Error, output, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(output: any, force = false) {
|
||||||
|
this.writeLog(LoggingLevel.Warning, output, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(output: any, force = false) {
|
||||||
|
this.writeLog(LoggingLevel.Info, output, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(output: any, force = false) {
|
||||||
|
this.writeLog(LoggingLevel.Debug, output, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
public verbose(output: any, force = false) {
|
||||||
|
this.writeLog(LoggingLevel.Verbose, output, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected writeLog(level: LoggingLevel, output: any, force = false) {
|
||||||
|
const message = this.buildMessage(level, output)
|
||||||
|
if ( this.currentLevel >= level || force ) {
|
||||||
|
for ( const logger of this.registeredLoggers ) {
|
||||||
|
try {
|
||||||
|
logger.write(message)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('logging error', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected buildMessage(level: LoggingLevel, output: any): LogMessage {
|
||||||
|
return {
|
||||||
|
level,
|
||||||
|
output,
|
||||||
|
date: new Date,
|
||||||
|
callerName: this.getCallerInfo(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getCallerInfo(level = 5): string {
|
||||||
|
const e = new Error()
|
||||||
|
if ( !e.stack ) return 'Unknown'
|
||||||
|
|
||||||
|
return e.stack.split(/\s+at\s+/)
|
||||||
|
.slice(level)
|
||||||
|
.map((x: string): string => x.trim().split(' (')[0].split('.')[0].split(':')[0])[0]
|
||||||
|
}
|
||||||
|
}
|
11
src/tsconfig.json
Normal file
11
src/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./lib",
|
||||||
|
"strict": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user