TypeDoc all the thngs
This commit is contained in:
@@ -8,12 +8,18 @@ import {Inject} from "@extollo/di";
|
||||
import * as nodePath from 'path'
|
||||
import {Unit} from "../lifecycle/Unit";
|
||||
|
||||
/**
|
||||
* Interface describing a definition of a single canonical item loaded from the app.
|
||||
*/
|
||||
export interface CanonicalDefinition {
|
||||
canonicalName: string,
|
||||
originalName: string,
|
||||
imported: any,
|
||||
}
|
||||
|
||||
/**
|
||||
* Type alias for a function that resolves a canonical name to a canonical item, if one exists.
|
||||
*/
|
||||
export type CanonicalResolver<T> = (key: string) => T | undefined
|
||||
|
||||
/**
|
||||
@@ -25,6 +31,19 @@ export interface CanonicalReference {
|
||||
particular?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract unit type that loads items recursively from a directory structure, assigning
|
||||
* them normalized names ("canonical names"), and providing a way to fetch the resources
|
||||
* by name.
|
||||
*
|
||||
* @example
|
||||
* The Config service is a Canonical derivative that loads files ending with `.config.js`
|
||||
* from the `app/config` directory.
|
||||
*
|
||||
* If, for example, there is a config file `app/config/auth/Forms.config.js` (in the
|
||||
* generated code), it can be loaded by the canonical name `auth:Forms`.
|
||||
*
|
||||
*/
|
||||
export abstract class Canonical<T> extends Unit {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
@@ -81,18 +100,26 @@ export abstract class Canonical<T> extends Unit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of all loaded canonical names.
|
||||
*/
|
||||
public all(): string[] {
|
||||
return Object.keys(this.loadedItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Universal path to the base directory where this unit loads its canonical files from.
|
||||
*/
|
||||
public get path(): UniversalPath {
|
||||
return this.app().appPath(...this.appPath)
|
||||
}
|
||||
|
||||
/** Get the plural name of the canonical items provided by this unit. */
|
||||
public get canonicalItems() {
|
||||
return `${this.canonicalItem}s`
|
||||
}
|
||||
|
||||
/** Get a canonical item by key. */
|
||||
public get(key: string): T | undefined {
|
||||
if ( key.startsWith('@') ) {
|
||||
const [namespace, ...rest] = key.split(':')
|
||||
@@ -112,6 +139,34 @@ export abstract class Canonical<T> extends Unit {
|
||||
return this.loadedItems[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a namespace resolver with the canonical unit.
|
||||
*
|
||||
* Namespaces are canonical names that start with a particular key, beginning with the `@` character,
|
||||
* which resolve their resources using a resolver function.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const items = {
|
||||
* 'foo:bar': 123,
|
||||
* 'bob': 456,
|
||||
* }
|
||||
*
|
||||
* const resolver = (key: string) => items[key]
|
||||
*
|
||||
* canonical.registerNamespace('@mynamespace', resolver)
|
||||
* ```
|
||||
*
|
||||
* Now, the items in the `@mynamespace` namespace can be accessed like so:
|
||||
*
|
||||
* ```typescript
|
||||
* canonical.get('@mynamespace:foo:bar') // => 123
|
||||
* canonical.get('@mynamespace:bob') // => 456
|
||||
* ```
|
||||
*
|
||||
* @param name
|
||||
* @param resolver
|
||||
*/
|
||||
public registerNamespace(name: string, resolver: CanonicalResolver<T>) {
|
||||
if ( !name.startsWith('@') ) {
|
||||
throw new ErrorWithContext(`Canonical namespaces must start with @.`, { name })
|
||||
@@ -139,10 +194,20 @@ export abstract class Canonical<T> extends Unit {
|
||||
this.canon.registerCanonical(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for each canonical item loaded from a file. This function should do any setup necessary and return the item
|
||||
* that should be associated with the canonical name.
|
||||
* @param definition
|
||||
*/
|
||||
public async initCanonicalItem(definition: CanonicalDefinition): Promise<T> {
|
||||
return definition.imported.default ?? definition.imported[definition.canonicalName.split(':').reverse()[0]]
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the path to a file in the canonical items directory, create a CanonicalDefinition record from that file.
|
||||
* @param filePath
|
||||
* @protected
|
||||
*/
|
||||
protected async buildCanonicalDefinition(filePath: string): Promise<CanonicalDefinition> {
|
||||
const originalName = filePath.replace(this.path.toLocal, '').substr(1)
|
||||
const pathRegex = new RegExp(nodePath.sep, 'g')
|
||||
|
||||
@@ -5,12 +5,18 @@
|
||||
import {Canonical, CanonicalDefinition} from "./Canonical";
|
||||
import {Instantiable, isInstantiable} from "@extollo/di";
|
||||
|
||||
/**
|
||||
* Error thrown when the export of a canonical file is determined to be invalid.
|
||||
*/
|
||||
export class InvalidCanonicalExportError extends Error {
|
||||
constructor(name: string) {
|
||||
super(`Unable to import canonical item from "${name}". The default export of this file is invalid.`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of the Canonical unit whose files export classes which are instantiated using the global container.
|
||||
*/
|
||||
export class CanonicalInstantiable<T> extends Canonical<Instantiable<T>> {
|
||||
public async initCanonicalItem(definition: CanonicalDefinition): Promise<Instantiable<T>> {
|
||||
if ( isInstantiable(definition.imported.default) ) {
|
||||
@@ -23,4 +29,4 @@ export class CanonicalInstantiable<T> extends Canonical<Instantiable<T>> {
|
||||
|
||||
throw new InvalidCanonicalExportError(definition.originalName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
import {Canonical} from "./Canonical";
|
||||
|
||||
/**
|
||||
* Variant of the Canonical unit whose accessor allows accessing nested
|
||||
* properties on the resolved objects.
|
||||
*
|
||||
* @example
|
||||
* The Config unit is a CanonicalRecursive unit. So, once a config file is
|
||||
* resolved, a particular value in the config file can be retrieved as well:
|
||||
*
|
||||
* ```typescript
|
||||
* // app/config/my/config.config.ts
|
||||
* {
|
||||
* foo: {
|
||||
* bar: 123
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This can be accessed as:
|
||||
* ```typescript
|
||||
* config.get('my:config.foo.bar') // => 123
|
||||
* ```
|
||||
*/
|
||||
export class CanonicalRecursive extends Canonical<any> {
|
||||
public get(key: string, fallback?: any): any | undefined {
|
||||
const parts = key.split('.')
|
||||
|
||||
@@ -2,6 +2,14 @@ import {Canonical, CanonicalDefinition} from "./Canonical";
|
||||
import {isStaticClass, StaticClass} from "@extollo/di";
|
||||
import {InvalidCanonicalExportError} from "./CanonicalInstantiable";
|
||||
|
||||
/**
|
||||
* Variant of the Canonical unit whose files export static classes, and these static classes
|
||||
* are the exports of the class.
|
||||
*
|
||||
* @example
|
||||
* The Controllers class is CanonicalStatic. The various `.controller.ts` files export static
|
||||
* Controller classes, so the canonical items managed by the Controllers service are `Instantiable<Controller>`.
|
||||
*/
|
||||
export class CanonicalStatic<T, T2> extends Canonical<StaticClass<T, T2>> {
|
||||
public async initCanonicalItem(definition: CanonicalDefinition): Promise<StaticClass<T, T2>> {
|
||||
if ( isStaticClass(definition.imported.default) ) {
|
||||
|
||||
@@ -2,6 +2,9 @@ import {Singleton, Inject} from "@extollo/di";
|
||||
import {CanonicalRecursive} from "./CanonicalRecursive";
|
||||
import {Logging} from "./Logging";
|
||||
|
||||
/**
|
||||
* Canonical unit that loads configuration files from `app/configs`.
|
||||
*/
|
||||
@Singleton()
|
||||
export class Config extends CanonicalRecursive {
|
||||
@Inject()
|
||||
@@ -10,7 +13,11 @@ export class Config extends CanonicalRecursive {
|
||||
protected appPath: string[] = ['configs']
|
||||
protected suffix: string = '.config.js'
|
||||
protected canonicalItem: string = 'config'
|
||||
|
||||
/** If true, all the unique configuration keys will be stored for debugging. */
|
||||
protected recordConfigAccesses: boolean = false
|
||||
|
||||
/** Array of all unique accessed config keys, if `recordConfigAccesses` is true. */
|
||||
protected accessedKeys: string[] = []
|
||||
|
||||
public async up() {
|
||||
|
||||
@@ -3,6 +3,9 @@ import {Singleton, Instantiable} from "@extollo/di";
|
||||
import {Controller} from "../http/Controller";
|
||||
import {CanonicalDefinition} from "./Canonical";
|
||||
|
||||
/**
|
||||
* A canonical unit that loads the controller classes from `app/http/controllers`.
|
||||
*/
|
||||
@Singleton()
|
||||
export class Controllers extends CanonicalStatic<Instantiable<Controller>, Controller> {
|
||||
protected appPath = ['http', 'controllers']
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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() {
|
||||
this.canon.registerCanonical(this)
|
||||
|
||||
@@ -15,6 +15,10 @@ import {error} from "../http/response/ErrorResponseFactory";
|
||||
import {ExecuteResolvedRoutePreflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule";
|
||||
import {ExecuteResolvedRoutePostflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule";
|
||||
|
||||
/**
|
||||
* Application unit that starts the HTTP/S server, creates Request and Response objects
|
||||
* for it, and handles those requests using the HTTPKernel.
|
||||
*/
|
||||
@Singleton()
|
||||
export class HTTPServer extends Unit {
|
||||
@Inject()
|
||||
@@ -23,6 +27,7 @@ export class HTTPServer extends Unit {
|
||||
@Inject()
|
||||
protected readonly kernel!: HTTPKernel
|
||||
|
||||
/** The underlying native Node.js server. */
|
||||
protected server?: Server
|
||||
|
||||
public async up() {
|
||||
|
||||
@@ -1,53 +1,120 @@
|
||||
import {Logger, LoggingLevel, LogMessage} from "@extollo/util";
|
||||
import {Singleton} from "@extollo/di";
|
||||
|
||||
/**
|
||||
* A singleton service that manages loggers registered in the application, and
|
||||
* can be used to log output to all of them based on the configured logging level.
|
||||
*
|
||||
* This should be used in place of `console.log` as it also supports logging to
|
||||
* external locations.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* logging.info('Info level!')
|
||||
* logging.debug('Some debugging information...')
|
||||
* logging.warn('A warning!', true) // true, to force it to show, regardless of logging level.
|
||||
* ```
|
||||
*/
|
||||
@Singleton()
|
||||
export class Logging {
|
||||
/** Array of Logger implementations that should be logged to. */
|
||||
protected registeredLoggers: Logger[] = []
|
||||
|
||||
/** The currently configured logging level. */
|
||||
protected currentLevel: LoggingLevel = LoggingLevel.Warning
|
||||
|
||||
/** Register a Logger implementation with this service. */
|
||||
public registerLogger(logger: Logger) {
|
||||
if ( !this.registeredLoggers.includes(logger) ) {
|
||||
this.registeredLoggers.push(logger)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a Logger implementation from this service, if it is registered.
|
||||
* @param logger
|
||||
*/
|
||||
public unregisterLogger(logger: Logger) {
|
||||
this.registeredLoggers = this.registeredLoggers.filter(x => x !== logger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current logging level.
|
||||
*/
|
||||
public get level(): LoggingLevel {
|
||||
return this.currentLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current logging level.
|
||||
* @param level
|
||||
*/
|
||||
public set level(level: LoggingLevel) {
|
||||
this.currentLevel = level
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a success-level output to the logs.
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
*/
|
||||
public success(output: any, force = false) {
|
||||
this.writeLog(LoggingLevel.Success, output, force)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an error-level output to the logs.
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
*/
|
||||
public error(output: any, force = false) {
|
||||
this.writeLog(LoggingLevel.Error, output, force)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a warning-level output to the logs.
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
*/
|
||||
public warn(output: any, force = false) {
|
||||
this.writeLog(LoggingLevel.Warning, output, force)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an info-level output to the logs.
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
*/
|
||||
public info(output: any, force = false) {
|
||||
this.writeLog(LoggingLevel.Info, output, force)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a debugging-level output to the logs.
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
*/
|
||||
public debug(output: any, force = false) {
|
||||
this.writeLog(LoggingLevel.Debug, output, force)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a verbose-level output to the logs.
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
*/
|
||||
public verbose(output: any, force = false) {
|
||||
this.writeLog(LoggingLevel.Verbose, output, force)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to write the given output, at the given logging level, to
|
||||
* all of the registered loggers.
|
||||
* @param level
|
||||
* @param output
|
||||
* @param force - if true, output even if outside the current logging level
|
||||
* @protected
|
||||
*/
|
||||
protected writeLog(level: LoggingLevel, output: any, force = false) {
|
||||
const message = this.buildMessage(level, output)
|
||||
if ( this.currentLevel >= level || force ) {
|
||||
@@ -61,6 +128,12 @@ export class Logging {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a level and output item, build a formatted LogMessage with date and caller.
|
||||
* @param level
|
||||
* @param output
|
||||
* @protected
|
||||
*/
|
||||
protected buildMessage(level: LoggingLevel, output: any): LogMessage {
|
||||
return {
|
||||
level,
|
||||
@@ -70,6 +143,11 @@ export class Logging {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the object that called the log method using error traces.
|
||||
* @param level
|
||||
* @protected
|
||||
*/
|
||||
protected getCallerInfo(level = 5): string {
|
||||
const e = new Error()
|
||||
if ( !e.stack ) return 'Unknown'
|
||||
|
||||
@@ -3,6 +3,9 @@ import {Singleton, Instantiable} from "@extollo/di";
|
||||
import {CanonicalDefinition} from "./Canonical";
|
||||
import {Middleware} from "../http/routing/Middleware";
|
||||
|
||||
/**
|
||||
* A canonical unit that loads the middleware classes from `app/http/middlewares`.
|
||||
*/
|
||||
@Singleton()
|
||||
export class Middlewares extends CanonicalStatic<Instantiable<Middleware>, Middleware> {
|
||||
protected appPath = ['http', 'middlewares']
|
||||
|
||||
@@ -6,6 +6,9 @@ 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.
|
||||
*/
|
||||
@Singleton()
|
||||
export class Routing extends Unit {
|
||||
@Inject()
|
||||
@@ -35,12 +38,21 @@ export class Routing extends Unit {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an HTTPMethod and route path, return the Route instance that matches them,
|
||||
* if one exists.
|
||||
* @param method
|
||||
* @param path
|
||||
*/
|
||||
public match(method: HTTPMethod, path: string): Route | undefined {
|
||||
return this.compiledRoutes.firstWhere(route => {
|
||||
return route.match(method, path)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the universal path to the root directory of the route definitions.
|
||||
*/
|
||||
public get path(): UniversalPath {
|
||||
return this.app().appPath('http', 'routes')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user