Setup eslint and enforce rules
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-06-02 22:36:25 -05:00
parent 82e7a1f299
commit 1d5056b753
149 changed files with 6104 additions and 3114 deletions

View File

@@ -1,5 +1,5 @@
import {Canonical} from "./Canonical";
import {Singleton} from "../di";
import {Canonical} from './Canonical'
import {Singleton} from '../di'
/**
* Error throw when a duplicate canonical key is registered.
@@ -40,7 +40,9 @@ export class Canon {
* @return Canonical
*/
resource<T>(key: string): Canonical<T> {
if ( !this.resources[key] ) throw new NoSuchCanonicalResolverKeyError(key)
if ( !this.resources[key] ) {
throw new NoSuchCanonicalResolverKeyError(key)
}
return this.resources[key] as Canonical<T>
}
@@ -48,9 +50,11 @@ export class Canon {
* Register a canonical resource.
* @param {Canonical} unit
*/
registerCanonical(unit: Canonical<any>) {
registerCanonical(unit: Canonical<any>): void {
const key = unit.canonicalItems
if ( this.resources[key] ) throw new DuplicateResolverKeyError(key)
if ( this.resources[key] ) {
throw new DuplicateResolverKeyError(key)
}
this.resources[key] = unit
}
}

View File

@@ -1,12 +1,12 @@
/**
* Base type for a canonical definition.
*/
import {Canon} from "./Canon";
import {universalPath, UniversalPath, ErrorWithContext} from "../util";
import {Logging} from "./Logging";
import {Inject} from "../di";
import {Canon} from './Canon'
import {universalPath, UniversalPath, ErrorWithContext} from '../util'
import {Logging} from './Logging'
import {Inject} from '../di'
import * as nodePath from 'path'
import {Unit} from "../lifecycle/Unit";
import {Unit} from '../lifecycle/Unit'
/**
* Interface describing a definition of a single canonical item loaded from the app.
@@ -61,14 +61,14 @@ export abstract class Canonical<T> extends Unit {
* The file suffix of files in the base path that should be loaded.
* @type string
*/
protected suffix: string = '.js'
protected suffix = '.js'
/**
* The singular, programmatic name of one of these canonical items.
* @example middleware
* @type string
*/
protected canonicalItem: string = ''
protected canonicalItem = ''
/**
* Object mapping canonical names to loaded file references.
@@ -90,13 +90,15 @@ export abstract class Canonical<T> extends Unit {
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 rscLess = rscParts.length > 1 ? rscParts[1] : rscParts[0]
const prtParts = rscLess.split('.')
const item = prtParts[0]
const particular = prtParts.length > 1 ? prtParts.slice(1).join('.') : undefined
return {
resource, item, particular
resource,
item,
particular,
}
}
@@ -115,7 +117,7 @@ export abstract class Canonical<T> extends Unit {
}
/** Get the plural name of the canonical items provided by this unit. */
public get canonicalItems() {
public get canonicalItems(): string {
return `${this.canonicalItem}s`
}
@@ -167,7 +169,7 @@ export abstract class Canonical<T> extends Unit {
* @param name
* @param resolver
*/
public registerNamespace(name: string, resolver: CanonicalResolver<T>) {
public registerNamespace(name: string, resolver: CanonicalResolver<T>): void {
if ( !name.startsWith('@') ) {
throw new ErrorWithContext(`Canonical namespaces must start with @.`, { name })
}
@@ -179,7 +181,7 @@ export abstract class Canonical<T> extends Unit {
this.loadedNamespaces[name] = resolver
}
public async up() {
public async up(): Promise<void> {
for await ( const entry of this.path.walk() ) {
if ( !entry.endsWith(this.suffix) ) {
this.logging.debug(`Skipping file with invalid suffix: ${entry}`)
@@ -212,14 +214,20 @@ export abstract class Canonical<T> extends Unit {
const originalName = filePath.replace(this.path.toLocal, '').substr(1)
const pathRegex = new RegExp(nodePath.sep, 'g')
const canonicalName = originalName.replace(pathRegex, ':')
.split('').reverse().join('')
.split('')
.reverse()
.join('')
.substr(this.suffix.length)
.split('').reverse().join('')
.split('')
.reverse()
.join('')
const fullUniversalPath = universalPath(filePath)
this.logging.verbose(`Importing from: ${fullUniversalPath}`)
const imported = await import(fullUniversalPath.toLocal)
return { canonicalName, originalName, imported }
return { canonicalName,
originalName,
imported }
}
}

View File

@@ -2,8 +2,8 @@
* 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 "../di";
import {Canonical, CanonicalDefinition} from './Canonical'
import {Instantiable, isInstantiable} from '../di'
/**
* Error thrown when the export of a canonical file is determined to be invalid.

View File

@@ -1,4 +1,4 @@
import {Canonical} from "./Canonical";
import {Canonical} from './Canonical'
/**
* Variant of the Canonical unit whose accessor allows accessing nested
@@ -23,7 +23,7 @@ import {Canonical} from "./Canonical";
* ```
*/
export class CanonicalRecursive extends Canonical<any> {
public get(key: string, fallback?: any): any | undefined {
public get(key: string, fallback?: unknown): any | undefined {
const parts = key.split('.')
let currentValue = this.loadedItems
for ( const part of parts ) {

View File

@@ -1,6 +1,6 @@
import {Canonical, CanonicalDefinition} from "./Canonical";
import {isStaticClass, StaticClass} from "../di";
import {InvalidCanonicalExportError} from "./CanonicalInstantiable";
import {Canonical, CanonicalDefinition} from './Canonical'
import {isStaticClass, StaticClass} from '../di'
import {InvalidCanonicalExportError} from './CanonicalInstantiable'
/**
* Variant of the Canonical unit whose files export static classes, and these static classes

View File

@@ -1,6 +1,6 @@
import {Singleton, Inject} from "../di";
import {CanonicalRecursive} from "./CanonicalRecursive";
import {Logging} from "./Logging";
import {Singleton, Inject} from '../di'
import {CanonicalRecursive} from './CanonicalRecursive'
import {Logging} from './Logging'
/**
* Canonical unit that loads configuration files from `app/configs`.
@@ -11,16 +11,18 @@ export class Config extends CanonicalRecursive {
protected readonly logging!: Logging
protected appPath: string[] = ['configs']
protected suffix: string = '.config.js'
protected canonicalItem: string = 'config'
protected suffix = '.config.js'
protected canonicalItem = 'config'
/** If true, all the unique configuration keys will be stored for debugging. */
protected recordConfigAccesses: boolean = false
protected recordConfigAccesses = false
/** Array of all unique accessed config keys, if `recordConfigAccesses` is true. */
protected accessedKeys: string[] = []
public async up() {
public async up(): Promise<void> {
await super.up()
if ( this.get('server.debug', false) ) {
@@ -29,7 +31,7 @@ export class Config extends CanonicalRecursive {
}
}
public async down() {
public async down(): Promise<void> {
await super.down()
if ( this.recordConfigAccesses && this.accessedKeys.length ) {
@@ -38,11 +40,11 @@ export class Config extends CanonicalRecursive {
}
}
public get(key: string, fallback?: any): any {
public get(key: string, fallback?: unknown): any {
if ( this.recordConfigAccesses && !this.accessedKeys.includes(key) ) {
this.accessedKeys.push(key)
}
return super.get(key, fallback);
return super.get(key, fallback)
}
}

View File

@@ -1,7 +1,7 @@
import {CanonicalStatic} from "./CanonicalStatic";
import {Singleton, Instantiable} from "../di";
import {Controller} from "../http/Controller";
import {CanonicalDefinition} from "./Canonical";
import {CanonicalStatic} from './CanonicalStatic'
import {Singleton, Instantiable, StaticClass} from '../di'
import {Controller} from '../http/Controller'
import {CanonicalDefinition} from './Canonical'
/**
* A canonical unit that loads the controller classes from `app/http/controllers`.
@@ -9,10 +9,12 @@ import {CanonicalDefinition} from "./Canonical";
@Singleton()
export class Controllers extends CanonicalStatic<Instantiable<Controller>, Controller> {
protected appPath = ['http', 'controllers']
protected canonicalItem = 'controller'
protected suffix = '.controller.js'
public async initCanonicalItem(definition: CanonicalDefinition) {
public async initCanonicalItem(definition: CanonicalDefinition): Promise<StaticClass<Instantiable<Controller>, Controller>> {
const item = await super.initCanonicalItem(definition)
if ( !(item.prototype instanceof Controller) ) {
throw new TypeError(`Invalid controller definition: ${definition.originalName}. Controllers must extend from @extollo/lib.http.Controller.`)

View File

@@ -1,11 +1,11 @@
import {Canonical} from "./Canonical";
import {Canonical} from './Canonical'
/**
* Canonical class used for faking canonical units. Here, the canonical resolver
* is registered with the global service, but no files are loaded from the filesystem.
*/
export class FakeCanonical<T> extends Canonical<T> {
public async up() {
public async up(): Promise<void> {
this.canon.registerCanonical(this)
}
}

View File

@@ -1,16 +1,16 @@
import {Unit} from "../lifecycle/Unit"
import {Inject, Singleton} from "../di"
import {Config} from "./Config"
import {Logging} from "./Logging"
import {Filesystem, ErrorWithContext} from "../util"
import {Unit} from '../lifecycle/Unit'
import {Inject, Singleton} from '../di'
import {Config} from './Config'
import {Logging} from './Logging'
import {Filesystem, ErrorWithContext} from '../util'
/**
* Error thrown when a function is called on a filesystem that does not exists in code.
*/
export class FilesystemDoesNotExist extends ErrorWithContext {
constructor(
message: string = 'The specified filesystem does not exist.',
context: {[key: string]: any} = {}
message = 'The specified filesystem does not exist.',
context: {[key: string]: any} = {},
) {
super(message, context)
}
@@ -57,6 +57,7 @@ export class FilesystemDoesNotExist extends ErrorWithContext {
@Singleton()
export class Files extends Unit {
protected filesystems: {[key: string]: Filesystem} = {}
protected defaultFilesystem?: Filesystem
@Inject()
@@ -65,11 +66,14 @@ export class Files extends Unit {
@Inject()
protected readonly logging!: Logging
async up() {
async up(): Promise<void> {
const config = this.config.get('server.filesystems', {})
const promises = []
for ( const key in config ) {
if ( !config.hasOwnProperty(key) ) continue;
if ( !Object.prototype.hasOwnProperty.call(config, key) ) {
continue
}
if ( config[key]?.driver?.prototype instanceof Filesystem ) {
this.logging.verbose(`Registering filesystem '${key}' with driver ${config[key].driver.name}...`)
@@ -108,7 +112,7 @@ export class Files extends Unit {
}
}
async down() {
async down(): Promise<void> {
await Promise.all(Object.values(this.filesystems).map(fs => fs.close()))
}
@@ -116,12 +120,12 @@ export class Files extends Unit {
* Returns true if a filesystem with the given name exists.
* @param key
*/
hasFilesystem(key?: string) {
hasFilesystem(key?: string): boolean {
if ( !key ) {
return !!this.defaultFilesystem
return Boolean(this.defaultFilesystem)
}
return !!this.filesystems[key]
return Boolean(this.filesystems[key])
}
/**
@@ -149,7 +153,7 @@ export class Files extends Unit {
* @param key
* @param fs
*/
registerFilesystem(key: string, fs: Filesystem) {
registerFilesystem(key: string, fs: Filesystem): void {
if ( this.hasFilesystem(key) ) {
this.logging.warn(`Overwriting filesystem with duplicate name: ${key}`)
}

View File

@@ -1,21 +1,21 @@
import {Inject, Singleton} from "../di"
import {HTTPStatus, withTimeout} from "../util"
import {Unit} from "../lifecycle/Unit";
import {createServer, IncomingMessage, Server, ServerResponse} from "http";
import {Logging} from "./Logging";
import {Request} from "../http/lifecycle/Request";
import {HTTPKernel} from "../http/kernel/HTTPKernel";
import {PoweredByHeaderInjectionHTTPModule} from "../http/kernel/module/PoweredByHeaderInjectionHTTPModule";
import {SetSessionCookieHTTPModule} from "../http/kernel/module/SetSessionCookieHTTPModule";
import {InjectSessionHTTPModule} from "../http/kernel/module/InjectSessionHTTPModule";
import {PersistSessionHTTPModule} from "../http/kernel/module/PersistSessionHTTPModule";
import {MountActivatedRouteHTTPModule} from "../http/kernel/module/MountActivatedRouteHTTPModule";
import {ExecuteResolvedRouteHandlerHTTPModule} from "../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule";
import {error} from "../http/response/ErrorResponseFactory";
import {ExecuteResolvedRoutePreflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule";
import {ExecuteResolvedRoutePostflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule";
import {ParseIncomingBodyHTTPModule} from "../http/kernel/module/ParseIncomingBodyHTTPModule";
import {Config} from "./Config";
import {Inject, Singleton} from '../di'
import {HTTPStatus, withTimeout} from '../util'
import {Unit} from '../lifecycle/Unit'
import {createServer, IncomingMessage, RequestListener, Server, ServerResponse} from 'http'
import {Logging} from './Logging'
import {Request} from '../http/lifecycle/Request'
import {HTTPKernel} from '../http/kernel/HTTPKernel'
import {PoweredByHeaderInjectionHTTPModule} from '../http/kernel/module/PoweredByHeaderInjectionHTTPModule'
import {SetSessionCookieHTTPModule} from '../http/kernel/module/SetSessionCookieHTTPModule'
import {InjectSessionHTTPModule} from '../http/kernel/module/InjectSessionHTTPModule'
import {PersistSessionHTTPModule} from '../http/kernel/module/PersistSessionHTTPModule'
import {MountActivatedRouteHTTPModule} from '../http/kernel/module/MountActivatedRouteHTTPModule'
import {ExecuteResolvedRouteHandlerHTTPModule} from '../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule'
import {error} from '../http/response/ErrorResponseFactory'
import {ExecuteResolvedRoutePreflightHTTPModule} from '../http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule'
import {ExecuteResolvedRoutePostflightHTTPModule} from '../http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule'
import {ParseIncomingBodyHTTPModule} from '../http/kernel/module/ParseIncomingBodyHTTPModule'
import {Config} from './Config'
/**
* Application unit that starts the HTTP/S server, creates Request and Response objects
@@ -35,7 +35,7 @@ export class HTTPServer extends Unit {
/** The underlying native Node.js server. */
protected server?: Server
public async up() {
public async up(): Promise<void> {
const port = this.config.get('server.port', 8000)
// TODO register these by config
@@ -49,7 +49,7 @@ export class HTTPServer extends Unit {
ExecuteResolvedRoutePostflightHTTPModule.register(this.kernel)
ParseIncomingBodyHTTPModule.register(this.kernel)
await new Promise<void>((res, rej) => {
await new Promise<void>(res => {
this.server = createServer(this.handler)
this.server.listen(port, () => {
@@ -60,7 +60,7 @@ export class HTTPServer extends Unit {
})
}
public async down() {
public async down(): Promise<void> {
if ( this.server ) {
this.server.close(err => {
if ( err ) {
@@ -71,17 +71,17 @@ export class HTTPServer extends Unit {
}
}
public get handler() {
public get handler(): RequestListener {
const timeout = this.config.get('server.timeout', 10000)
return async (request: IncomingMessage, response: ServerResponse) => {
const extolloReq = new Request(request, response)
withTimeout(timeout, extolloReq.response.sent$.toPromise())
.onTime(req => {
.onTime(() => {
this.logging.verbose(`Request lifecycle finished on time. (Path: ${extolloReq.path})`)
})
.late(req => {
.late(() => {
if ( !extolloReq.bypassTimeout ) {
this.logging.warn(`Request lifecycle finished late, so an error response was returned! (Path: ${extolloReq.path})`)
}

View File

@@ -1,5 +1,5 @@
import {Logger, LoggingLevel, LogMessage} from "../util";
import {Singleton} from "../di";
import {Logger, LoggingLevel, LogMessage} from '../util'
import {Singleton} from '../di'
/**
* A singleton service that manages loggers registered in the application, and
@@ -24,18 +24,20 @@ export class Logging {
protected currentLevel: LoggingLevel = LoggingLevel.Warning
/** Register a Logger implementation with this service. */
public registerLogger(logger: Logger) {
public registerLogger(logger: Logger): this {
if ( !this.registeredLoggers.includes(logger) ) {
this.registeredLoggers.push(logger)
}
return this
}
/**
* Remove a Logger implementation from this service, if it is registered.
* @param logger
*/
public unregisterLogger(logger: Logger) {
public unregisterLogger(logger: Logger): this {
this.registeredLoggers = this.registeredLoggers.filter(x => x !== logger)
return this
}
/**
@@ -58,7 +60,7 @@ export class Logging {
* @param output
* @param force - if true, output even if outside the current logging level
*/
public success(output: any, force = false) {
public success(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Success, output, force)
}
@@ -67,7 +69,7 @@ export class Logging {
* @param output
* @param force - if true, output even if outside the current logging level
*/
public error(output: any, force = false) {
public error(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Error, output, force)
}
@@ -76,7 +78,7 @@ export class Logging {
* @param output
* @param force - if true, output even if outside the current logging level
*/
public warn(output: any, force = false) {
public warn(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Warning, output, force)
}
@@ -85,7 +87,7 @@ export class Logging {
* @param output
* @param force - if true, output even if outside the current logging level
*/
public info(output: any, force = false) {
public info(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Info, output, force)
}
@@ -94,7 +96,7 @@ export class Logging {
* @param output
* @param force - if true, output even if outside the current logging level
*/
public debug(output: any, force = false) {
public debug(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Debug, output, force)
}
@@ -103,7 +105,7 @@ export class Logging {
* @param output
* @param force - if true, output even if outside the current logging level
*/
public verbose(output: any, force = false) {
public verbose(output: unknown, force = false): void {
this.writeLog(LoggingLevel.Verbose, output, force)
}
@@ -115,14 +117,14 @@ export class Logging {
* @param force - if true, output even if outside the current logging level
* @protected
*/
protected writeLog(level: LoggingLevel, output: any, force = false) {
protected writeLog(level: LoggingLevel, output: unknown, force = false): void {
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)
console.error('logging error', e) // eslint-disable-line no-console
}
}
}
@@ -134,11 +136,11 @@ export class Logging {
* @param output
* @protected
*/
protected buildMessage(level: LoggingLevel, output: any): LogMessage {
protected buildMessage(level: LoggingLevel, output: unknown): LogMessage {
return {
level,
output,
date: new Date,
date: new Date(),
callerName: this.getCallerInfo(),
}
}
@@ -150,7 +152,9 @@ export class Logging {
*/
protected getCallerInfo(level = 5): string {
const e = new Error()
if ( !e.stack ) return 'Unknown'
if ( !e.stack ) {
return 'Unknown'
}
return e.stack.split(/\s+at\s+/)
.slice(level)

View File

@@ -1,7 +1,7 @@
import {CanonicalStatic} from "./CanonicalStatic";
import {Singleton, Instantiable} from "../di";
import {CanonicalDefinition} from "./Canonical";
import {Middleware} from "../http/routing/Middleware";
import {CanonicalStatic} from './CanonicalStatic'
import {Singleton, Instantiable, StaticClass} from '../di'
import {CanonicalDefinition} from './Canonical'
import {Middleware} from '../http/routing/Middleware'
/**
* A canonical unit that loads the middleware classes from `app/http/middlewares`.
@@ -9,10 +9,12 @@ import {Middleware} from "../http/routing/Middleware";
@Singleton()
export class Middlewares extends CanonicalStatic<Instantiable<Middleware>, Middleware> {
protected appPath = ['http', 'middlewares']
protected canonicalItem = 'middleware'
protected suffix = '.middleware.js'
public async initCanonicalItem(definition: CanonicalDefinition) {
public async initCanonicalItem(definition: CanonicalDefinition): Promise<StaticClass<Instantiable<Middleware>, Middleware>> {
const item = await super.initCanonicalItem(definition)
if ( !(item.prototype instanceof Middleware) ) {
throw new TypeError(`Invalid middleware definition: ${definition.originalName}. Controllers must extend from @extollo/lib.http.routing.Middleware.`)

View File

@@ -1,10 +1,10 @@
import {Singleton, Inject} from "../di"
import {UniversalPath, Collection} from "../util"
import {Unit} from "../lifecycle/Unit"
import {Logging} from "./Logging"
import {Route} from "../http/routing/Route";
import {HTTPMethod} from "../http/lifecycle/Request";
import {ViewEngineFactory} from "../views/ViewEngineFactory";
import {Singleton, Inject} from '../di'
import {UniversalPath, Collection} from '../util'
import {Unit} from '../lifecycle/Unit'
import {Logging} from './Logging'
import {Route} from '../http/routing/Route'
import {HTTPMethod} from '../http/lifecycle/Request'
import {ViewEngineFactory} from '../views/ViewEngineFactory'
/**
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
@@ -16,8 +16,8 @@ export class Routing extends Unit {
protected compiledRoutes: Collection<Route> = new Collection<Route>()
public async up() {
this.app().registerFactory(new ViewEngineFactory());
public async up(): Promise<void> {
this.app().registerFactory(new ViewEngineFactory())
for await ( const entry of this.path.walk() ) {
if ( !entry.endsWith('.routes.js') ) {