JSDoc all the things!
This commit is contained in:
@@ -4,6 +4,10 @@ import ResponseFactory from './response/ResponseFactory.ts'
|
||||
import * as api from '../support/api.ts'
|
||||
import JSONResponseFactory from './response/JSONResponseFactory.ts'
|
||||
|
||||
/**
|
||||
* HTTP controller which wraps its handlers output in JSON response factories, if appropriate.
|
||||
* @extends Controller
|
||||
*/
|
||||
export default class ApiController extends Controller {
|
||||
public get_bound_method(method_name: string): (...args: any[]) => any {
|
||||
// @ts-ignore
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import AppClass from '../lifecycle/AppClass.ts'
|
||||
|
||||
/**
|
||||
* Base class for an HTTP controller.
|
||||
* @extends AppClass
|
||||
*/
|
||||
export default class Controller extends AppClass {
|
||||
|
||||
}
|
||||
|
||||
@@ -2,32 +2,63 @@ import { Injectable } from '../../../di/src/decorator/Injection.ts'
|
||||
import { getCookies, setCookie, delCookie, ServerRequest } from '../external/http.ts'
|
||||
import { InMemCache } from '../support/InMemCache.ts'
|
||||
import { HTTPRequest } from './type/HTTPRequest.ts'
|
||||
import {logger} from "../service/logging/global.ts";
|
||||
|
||||
/**
|
||||
* Base type representing a parsed cookie.
|
||||
*/
|
||||
export interface Cookie {
|
||||
key: string,
|
||||
original_value: string,
|
||||
value: any,
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing what might be a cookie, or might be undefined.
|
||||
*/
|
||||
export type MaybeCookie = Cookie | undefined
|
||||
|
||||
// TODO cookie options (http only, expires, &c.)
|
||||
/**
|
||||
* Base class for managing cookies.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CookieJar {
|
||||
/**
|
||||
* Cache of parsed cookie values.
|
||||
* @type object
|
||||
*/
|
||||
protected _parsed: { [key: string]: string } = {}
|
||||
|
||||
/**
|
||||
* Cache of cookie values.
|
||||
* @type InMemCache
|
||||
*/
|
||||
protected _cache = new InMemCache()
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The associated request.
|
||||
* @type HTTPRequest
|
||||
*/
|
||||
protected request: HTTPRequest,
|
||||
) {
|
||||
this._parsed = getCookies(this.request.to_native)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw value of a cookie string, if it is defined.
|
||||
* @param {string} key
|
||||
* @return string | undefined
|
||||
*/
|
||||
public async get_raw(key: string): Promise<string | undefined> {
|
||||
return this._parsed[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parsed value of a cookie, if it is defined.
|
||||
* @param {string} key
|
||||
* @return Promise<MaybeCookie>
|
||||
*/
|
||||
public async get(key: string): Promise<MaybeCookie> {
|
||||
// Try the cache
|
||||
if ( await this._cache.has(key) ) {
|
||||
@@ -52,6 +83,12 @@ export class CookieJar {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie for the given key to the serialized value.
|
||||
* @param {string} key
|
||||
* @param value
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async set(key: string, value: any): Promise<void> {
|
||||
const original_value = btoa(JSON.stringify(value))
|
||||
const cookie = {
|
||||
@@ -64,10 +101,20 @@ export class CookieJar {
|
||||
setCookie(this.request.response, { name: key, value: original_value })
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given cookie exists.
|
||||
* @param {string} key
|
||||
* @return Promise<boolean>
|
||||
*/
|
||||
public async has(key: string): Promise<boolean> {
|
||||
return (await this._cache.has(key)) || key in this._parsed
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given cookie, if it exists.
|
||||
* @param {string} key
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async delete(key: string): Promise<void> {
|
||||
await this._cache.drop(key)
|
||||
delCookie(this.request.response, key)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import AppClass from '../lifecycle/AppClass.ts'
|
||||
|
||||
/**
|
||||
* Base class for HTTP middleware.
|
||||
* @extends AppClass
|
||||
*/
|
||||
export default class Middleware extends AppClass {
|
||||
|
||||
}
|
||||
|
||||
@@ -7,46 +7,129 @@ import { Injectable } from '../../../di/src/decorator/Injection.ts'
|
||||
import SessionInterface from './session/SessionInterface.ts'
|
||||
import ActivatedRoute from './routing/ActivatedRoute.ts'
|
||||
|
||||
/**
|
||||
* Base class for Daton-managed HTTP requests.
|
||||
* @implements HTTPRequest
|
||||
*/
|
||||
@Injectable()
|
||||
export class Request implements HTTPRequest {
|
||||
/**
|
||||
* The associated response object.
|
||||
* @type HTTPResponse
|
||||
*/
|
||||
public readonly response: HTTPResponse
|
||||
|
||||
/**
|
||||
* The base raw Deno request.
|
||||
* @type ServerRequest
|
||||
*/
|
||||
private readonly _deno_req: ServerRequest
|
||||
|
||||
/**
|
||||
* The parsed body.
|
||||
*/
|
||||
private _body: any
|
||||
|
||||
/**
|
||||
* The parsed query params.
|
||||
* @type object
|
||||
*/
|
||||
private _query: { [key: string]: any } = {}
|
||||
|
||||
/**
|
||||
* The associated session.
|
||||
* @type SessionInterface
|
||||
*/
|
||||
private _session!: SessionInterface
|
||||
|
||||
/**
|
||||
* The matched, mounted route.
|
||||
* @type ActivatedRoute
|
||||
*/
|
||||
private _activated_route!: ActivatedRoute
|
||||
|
||||
/**
|
||||
* The incoming URL.
|
||||
* @type string
|
||||
*/
|
||||
public readonly url: string
|
||||
|
||||
/**
|
||||
* The incoming request method.
|
||||
* @type string
|
||||
*/
|
||||
public readonly method: string
|
||||
|
||||
/**
|
||||
* The incoming HTTP protocol.
|
||||
* @type HTTPProtocol
|
||||
*/
|
||||
public readonly protocol: HTTPProtocol
|
||||
|
||||
/**
|
||||
* The underlying Deno connection.
|
||||
* @type Deno.Conn
|
||||
*/
|
||||
public readonly connection: Deno.Conn
|
||||
|
||||
/**
|
||||
* True if the request used HTTPS.
|
||||
* @type boolean
|
||||
*/
|
||||
public readonly secure: boolean = false
|
||||
|
||||
/**
|
||||
* The incoming headers.
|
||||
*/
|
||||
public get headers() {
|
||||
return this._deno_req.headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying Deno request.
|
||||
* @type ServerRequest
|
||||
*/
|
||||
get to_native(): ServerRequest {
|
||||
return this._deno_req
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookies for the response.
|
||||
* @type CookieJar
|
||||
*/
|
||||
get cookies() {
|
||||
return this.response.cookies
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated session.
|
||||
* @type SessionInterface
|
||||
*/
|
||||
get session(): SessionInterface {
|
||||
return this._session
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated session.
|
||||
* @param {SessionInterface} session
|
||||
*/
|
||||
set session(session: SessionInterface) {
|
||||
if ( !this._session )
|
||||
this._session = session
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activated route mounted to this request.
|
||||
* @type ActivatedRoute
|
||||
*/
|
||||
get route(): ActivatedRoute {
|
||||
return this._activated_route
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the given route to the request.
|
||||
* @param {ActivatedRoute} route
|
||||
*/
|
||||
set route(route: ActivatedRoute) {
|
||||
if ( !this._activated_route )
|
||||
this._activated_route = route
|
||||
@@ -54,6 +137,9 @@ export class Request implements HTTPRequest {
|
||||
|
||||
constructor(
|
||||
protected utility: Utility,
|
||||
/**
|
||||
* The raw Deno request.
|
||||
*/
|
||||
from: ServerRequest
|
||||
) {
|
||||
this._deno_req = from
|
||||
@@ -68,6 +154,10 @@ export class Request implements HTTPRequest {
|
||||
this.response = new Response(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the request for the Daton framework.
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public async prepare() {
|
||||
this._body = await Deno.readAll(this._deno_req.body)
|
||||
|
||||
@@ -83,6 +173,10 @@ export class Request implements HTTPRequest {
|
||||
this._query = params
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given response.
|
||||
* @param res
|
||||
*/
|
||||
respond(res: any) {
|
||||
return this._deno_req.respond(res)
|
||||
}
|
||||
@@ -90,26 +184,50 @@ export class Request implements HTTPRequest {
|
||||
// public body: RequestBody = {}
|
||||
// public original_body: RequestBody = {}
|
||||
|
||||
/**
|
||||
* Get the remote host information.
|
||||
* @type RemoteHost
|
||||
*/
|
||||
get remote() {
|
||||
return this.connection.remoteAddr as RemoteHost
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw body from the request.
|
||||
* @type string
|
||||
*/
|
||||
get body() {
|
||||
return this._body
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params.
|
||||
* @type object
|
||||
*/
|
||||
get query() {
|
||||
return this._query
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the incoming host name.
|
||||
* @type string
|
||||
*/
|
||||
get hostname() {
|
||||
return this.headers.get('host')?.split(':')[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the incoming path of the route.
|
||||
* @type string
|
||||
*/
|
||||
get path() {
|
||||
return this.url.split('?')[0].split('#')[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the request is an XHR incoming.
|
||||
* @type boolean
|
||||
*/
|
||||
get xhr() {
|
||||
return this.headers.get('x-requested-with')?.toLowerCase() === 'xmlhttprequest'
|
||||
}
|
||||
|
||||
@@ -3,26 +3,74 @@ import {HTTPRequest} from './type/HTTPRequest.ts'
|
||||
import {ServerRequest} from '../external/http.ts'
|
||||
import {CookieJar} from './CookieJar.ts'
|
||||
|
||||
/**
|
||||
* Base class for a Daton-managed response.
|
||||
* @implements HTTPResponse
|
||||
*/
|
||||
export class Response implements HTTPResponse {
|
||||
/**
|
||||
* The outgoing HTTP status.
|
||||
* @type HTTPStatus
|
||||
*/
|
||||
public status = 200
|
||||
|
||||
/**
|
||||
* The response headers.
|
||||
* @type Headers
|
||||
*/
|
||||
public headers = new Headers()
|
||||
|
||||
/**
|
||||
* The response body.
|
||||
* @type string
|
||||
*/
|
||||
public body = ''
|
||||
|
||||
/**
|
||||
* The cookie manager.
|
||||
* @type CookieJar
|
||||
*/
|
||||
public readonly cookies: CookieJar
|
||||
|
||||
/**
|
||||
* The raw Deno request
|
||||
* @type ServerRequest
|
||||
*/
|
||||
private readonly _deno_req: ServerRequest
|
||||
|
||||
/**
|
||||
* The associated Daton request.
|
||||
* @type HTTPRequest
|
||||
*/
|
||||
private readonly _request: HTTPRequest
|
||||
|
||||
/**
|
||||
* True if the response has been sent.
|
||||
* @type boolean
|
||||
*/
|
||||
private _sent = false
|
||||
|
||||
/**
|
||||
* True if the response has been send.
|
||||
* @type boolean
|
||||
*/
|
||||
get sent() {
|
||||
return this._sent
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new response
|
||||
* @param {HTTPRequest} to - the associated request
|
||||
*/
|
||||
constructor(to: HTTPRequest) {
|
||||
this._deno_req = to.to_native
|
||||
this._request = to
|
||||
this.cookies = new CookieJar(to)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the response.
|
||||
*/
|
||||
send() {
|
||||
this._sent = true
|
||||
return this._deno_req.respond(this)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { HTTPRequest } from './type/HTTPRequest.ts'
|
||||
import { Request } from './Request.ts'
|
||||
|
||||
/**
|
||||
* Base class for requests made with HTTPS.
|
||||
* @extends Request
|
||||
* @implements HTTPRequest
|
||||
*/
|
||||
export default class SecureRequest extends Request implements HTTPRequest {
|
||||
public readonly secure: boolean = true
|
||||
}
|
||||
|
||||
@@ -2,8 +2,16 @@ import ResponseFactory from './ResponseFactory.ts'
|
||||
import {Rehydratable} from '../../support/Rehydratable.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
|
||||
/**
|
||||
* Response factory that returns a JSON object of the state of a rehydratable object.
|
||||
* @extends ResponseFactory
|
||||
*/
|
||||
export default class DehydratedStateResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The object to dehydrate.
|
||||
* @type Rehydratable
|
||||
*/
|
||||
public readonly rehydratable: Rehydratable
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -3,12 +3,32 @@ import {Request} from '../Request.ts'
|
||||
import * as api from '../../support/api.ts'
|
||||
import {HTTPStatus} from '../../const/http.ts'
|
||||
|
||||
/**
|
||||
* Response factory to render a handled request-level error.
|
||||
* @extends ResponseFactory
|
||||
*/
|
||||
export default class ErrorResponseFactory extends ResponseFactory {
|
||||
/**
|
||||
* The target output mode.
|
||||
* @type 'json' | 'html'
|
||||
*/
|
||||
protected target_mode: 'json' | 'html' = 'html'
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The error to display.
|
||||
* @type Error
|
||||
*/
|
||||
public readonly error: Error,
|
||||
/**
|
||||
* THe HTTP status to use for the response.
|
||||
* @type HTTPStatus
|
||||
*/
|
||||
status: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
/**
|
||||
* The output format.
|
||||
* @type 'json' | 'html'
|
||||
*/
|
||||
output: 'json' | 'html' = 'html',
|
||||
) {
|
||||
super()
|
||||
@@ -16,11 +36,21 @@ export default class ErrorResponseFactory extends ResponseFactory {
|
||||
this.mode(output)
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the output mode.
|
||||
* @param {'json' | 'html'} output
|
||||
* @return ErrorResponseFactory
|
||||
*/
|
||||
public mode(output: 'json' | 'html'): ErrorResponseFactory {
|
||||
this.target_mode = output
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this response factory to the given request's response.
|
||||
* @param {Request} request
|
||||
* @return Request
|
||||
*/
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
|
||||
@@ -35,6 +65,11 @@ export default class ErrorResponseFactory extends ResponseFactory {
|
||||
return request
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the HTML display for the given error.
|
||||
* @param {Error} error
|
||||
* @return string
|
||||
*/
|
||||
protected build_html(error: Error) {
|
||||
return `
|
||||
<b>Sorry, an unexpected error occurred while processing your request.</b>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import ResponseFactory from './ResponseFactory.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
|
||||
/**
|
||||
* Response factory that writes a string as HTML.
|
||||
* @extends ResponseFactory
|
||||
*/
|
||||
export default class HTMLResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
public readonly value: string,
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import ErrorResponseFactory from './ErrorResponseFactory.ts'
|
||||
import HTTPError from '../../error/HTTPError.ts'
|
||||
|
||||
/**
|
||||
* Response factory that renders a given HTTP error.
|
||||
* @extends ErrorResponseFactory
|
||||
*/
|
||||
export default class HTTPErrorResponseFactory extends ErrorResponseFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The HTTP error to render.
|
||||
* @type HTTPError
|
||||
*/
|
||||
public readonly error: HTTPError,
|
||||
/**
|
||||
* The output format.
|
||||
* @type 'json' | 'html'
|
||||
*/
|
||||
output: 'json' | 'html',
|
||||
) {
|
||||
super(error, error.http_status, output)
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import ResponseFactory from './ResponseFactory.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
|
||||
/**
|
||||
* Response factory that writes the given value as JSON.
|
||||
* @extends ResponseFactory
|
||||
*/
|
||||
export default class JSONResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The value to be JSON serialized and written.
|
||||
*/
|
||||
public readonly value: any
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -2,9 +2,20 @@ import ResponseFactory from './ResponseFactory.ts'
|
||||
import ViewEngine from '../../unit/ViewEngine.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
|
||||
/**
|
||||
* Response factory that renders a partial view as HTML.
|
||||
* @return ResponseFactory
|
||||
*/
|
||||
export default class PartialViewResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The view name.
|
||||
* @type string
|
||||
*/
|
||||
public readonly view: string,
|
||||
/**
|
||||
* Optionally, the response context.
|
||||
*/
|
||||
public readonly context?: any,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -2,14 +2,32 @@ import AppClass from '../../lifecycle/AppClass.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
import {HTTPStatus} from '../../const/http.ts'
|
||||
|
||||
/**
|
||||
* A base class that renders a response to a request.
|
||||
* @extends AppClass
|
||||
*/
|
||||
export default abstract class ResponseFactory extends AppClass {
|
||||
/**
|
||||
* The HTTP status to set on the response.
|
||||
* @type HTTPStatus
|
||||
*/
|
||||
protected target_status: HTTPStatus = HTTPStatus.OK
|
||||
|
||||
/**
|
||||
* Write the value to the response.
|
||||
* @param {Request} request
|
||||
* @return Promise<Request>
|
||||
*/
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request.response.status = this.target_status
|
||||
return request
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the response status.
|
||||
* @param {HTTPStatus} status
|
||||
* @return ResponseFactory
|
||||
*/
|
||||
public status(status: HTTPStatus): ResponseFactory {
|
||||
this.target_status = status
|
||||
return this
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import ResponseFactory from './ResponseFactory.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
|
||||
/**
|
||||
* Response factory that renders the given value as a string.
|
||||
* @return ResponseFactory
|
||||
*/
|
||||
export default class StringResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/**
|
||||
* Value to be written.
|
||||
* @type string
|
||||
*/
|
||||
public readonly value: string,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -2,10 +2,18 @@ import ResponseFactory from './ResponseFactory.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
import {HTTPStatus} from '../../const/http.ts'
|
||||
|
||||
/**
|
||||
* Response factory that sends a temporary redirect.
|
||||
* @extends ResponseFactory
|
||||
*/
|
||||
export default class TemporaryRedirectResponseFactory extends ResponseFactory {
|
||||
protected target_status: HTTPStatus = HTTPStatus.TEMPORARY_REDIRECT
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* Destination to redirect the user to.
|
||||
* @type string
|
||||
*/
|
||||
public readonly destination: string,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -2,10 +2,25 @@ import ResponseFactory from './ResponseFactory.ts'
|
||||
import ViewEngine from '../../unit/ViewEngine.ts'
|
||||
import {Request} from '../Request.ts'
|
||||
|
||||
/**
|
||||
* Response factory that renders the given view as HTML.
|
||||
* @extends ResponseFactory
|
||||
*/
|
||||
export default class ViewResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The view name.
|
||||
* @type string
|
||||
*/
|
||||
public readonly view: string,
|
||||
/**
|
||||
* Optionally, the view context.
|
||||
*/
|
||||
public readonly context?: any,
|
||||
/**
|
||||
* Optionally, the layout name.
|
||||
* @type string
|
||||
*/
|
||||
public readonly layout?: string,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -11,35 +11,80 @@ import HTTPError from '../../error/HTTPError.ts'
|
||||
import ViewResponseFactory from './ViewResponseFactory.ts'
|
||||
import PartialViewResponseFactory from './PartialViewResponseFactory.ts'
|
||||
|
||||
/**
|
||||
* Get a new JSON response factory that writes the given object as JSON.
|
||||
* @param value
|
||||
* @return JSONResponseFactory
|
||||
*/
|
||||
export function json(value: any): JSONResponseFactory {
|
||||
return make(JSONResponseFactory, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new HTML response factory that writes the given string as HTML.
|
||||
* @param value
|
||||
* @return HTMLResponseFactory
|
||||
*/
|
||||
export function html(value: string): HTMLResponseFactory {
|
||||
return make(HTMLResponseFactory, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new Error response factory that writes the given error.
|
||||
* @param {Error|string} error
|
||||
* @param {number} [status = 500] - the HTTP response status
|
||||
* @return ErrorResponseFactory
|
||||
*/
|
||||
export function error(error: Error | string, status: number = 500): ErrorResponseFactory {
|
||||
if ( typeof error === 'string' ) error = new Error(error)
|
||||
return make(ErrorResponseFactory, error, status)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new dehydrated response factory that dehydrates the given object and writes its state as JSON.
|
||||
* @param {Rehydratable} value
|
||||
* @return DehydratedStateResponseFactory
|
||||
*/
|
||||
export function dehydrate(value: Rehydratable): DehydratedStateResponseFactory {
|
||||
return make(DehydratedStateResponseFactory, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new temporary redirect response factory that redirects to the given destination.
|
||||
* @param {string} destination
|
||||
* @return TemporaryRedirectResponseFactory
|
||||
*/
|
||||
export function redirect(destination: string): TemporaryRedirectResponseFactory {
|
||||
return make(TemporaryRedirectResponseFactory, destination)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new http error response factory for the given http status code.
|
||||
* @param {HTTPStatus} status
|
||||
* @param {string} [message]
|
||||
* @return HTTPErrorResponseFactory
|
||||
*/
|
||||
export function http(status: HTTPStatus, message?: string): HTTPErrorResponseFactory {
|
||||
return make(HTTPErrorResponseFactory, new HTTPError(status, message))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new view response factory for the given view name, passing along context and layout.
|
||||
* @param {string} view
|
||||
* @param [context]
|
||||
* @param {string} [layout]
|
||||
* @return ViewResponseFactory
|
||||
*/
|
||||
export function view(view: string, context?: any, layout?: string): ViewResponseFactory {
|
||||
return make(ViewResponseFactory, view, context, layout)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new partial view response factory for the given view name, passing along context.
|
||||
* @param {string} view
|
||||
* @param [context]
|
||||
* @return PartialViewResponseFactory
|
||||
*/
|
||||
export function partial(view: string, context?: any): PartialViewResponseFactory {
|
||||
return make(PartialViewResponseFactory, view, context)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,32 @@ import AppClass from '../../lifecycle/AppClass.ts'
|
||||
import {Route, RouteParameters} from './Route.ts'
|
||||
import {RouteHandlers} from '../../unit/Routing.ts'
|
||||
|
||||
/**
|
||||
* Base class representing a route mounted to a request.
|
||||
* @extends AppClass
|
||||
*/
|
||||
export default class ActivatedRoute extends AppClass {
|
||||
/**
|
||||
* The incoming parameters parsed from the route.
|
||||
* @type RouteParameters
|
||||
*/
|
||||
public readonly params: RouteParameters
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The incoming route path string.
|
||||
* @type string
|
||||
*/
|
||||
public readonly incoming: string,
|
||||
/**
|
||||
* The matched route.
|
||||
* @type Route
|
||||
*/
|
||||
public readonly route: Route,
|
||||
/**
|
||||
* The handlers for this route.
|
||||
* @type RouteHandlers | undefined
|
||||
*/
|
||||
public readonly handlers: RouteHandlers | undefined,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -2,6 +2,10 @@ import {Route, RouteParameters, RouteSegment} from './Route.ts'
|
||||
import Utility from '../../service/utility/Utility.ts'
|
||||
import {make} from '../../../../di/src/global.ts'
|
||||
|
||||
/**
|
||||
* A route that contains route parameters and shallow wild-cards.
|
||||
* @extends Route
|
||||
*/
|
||||
export class ComplexRoute extends Route {
|
||||
public match(incoming: string): boolean {
|
||||
const base_parts = this.split(this.base)
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import {Route, RouteParameters} from './Route.ts'
|
||||
|
||||
/**
|
||||
* A route that contains deep wild-cards.
|
||||
* @extends Route
|
||||
*/
|
||||
export class DeepmatchRoute extends Route {
|
||||
/**
|
||||
* The built regex for parsing the route.
|
||||
* @type RegExp
|
||||
*/
|
||||
protected base_regex: RegExp
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* Base route definition.
|
||||
* @type string
|
||||
*/
|
||||
protected base: string
|
||||
) {
|
||||
super(base)
|
||||
@@ -30,6 +42,10 @@ export class DeepmatchRoute extends Route {
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the regex object for the given route parts.
|
||||
* @param {Array<string>} base_parts
|
||||
*/
|
||||
protected build_regex(base_parts: string[]) {
|
||||
const deepmatch_group = '([a-zA-Z0-9\\-\\_\\.\\/]+)' // allows for alphanum, -, _, ., and /
|
||||
const shallowmatch_group = '([a-zA-Z0-9\\-\\.\\_]+)' // allows for alphanum, -, ., and _
|
||||
|
||||
@@ -2,11 +2,27 @@ import {Route, RouteParameters} from './Route.ts'
|
||||
import {Logging} from '../../service/logging/Logging.ts'
|
||||
import {make} from '../../../../di/src/global.ts'
|
||||
|
||||
/**
|
||||
* Route that is defined and matched by regex
|
||||
* @extends Route
|
||||
*/
|
||||
export class RegExRoute extends Route {
|
||||
/**
|
||||
* Generated regex for the definition.
|
||||
* @type RegExp
|
||||
*/
|
||||
protected key_regex: RegExp
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* Route base string.
|
||||
* @type string
|
||||
*/
|
||||
protected base: string,
|
||||
/**
|
||||
* Regex key.
|
||||
* @type string
|
||||
*/
|
||||
protected key: string,
|
||||
) {
|
||||
super(base)
|
||||
@@ -48,6 +64,11 @@ export class RegExRoute extends Route {
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the regex for the given route, from its parsed key.
|
||||
* @param {string} key
|
||||
* @return RegExp
|
||||
*/
|
||||
protected build_regex(key: string) {
|
||||
if ( !key.startsWith('rex ') ) {
|
||||
throw new TypeError(`Invalid regular expression route pattern: ${key}`)
|
||||
|
||||
@@ -1,25 +1,56 @@
|
||||
import {logger} from "../../service/logging/global.ts";
|
||||
import {logger} from '../../service/logging/global.ts'
|
||||
|
||||
export type RouteParameters = { [key: string]: string }
|
||||
export type RouteSegment = { base: string, match: string | undefined }
|
||||
export type ZippedRouteSegments = RouteSegment[]
|
||||
|
||||
/**
|
||||
* Abstract base class representing a parsed and loaded route.
|
||||
*/
|
||||
export abstract class Route {
|
||||
constructor(
|
||||
/**
|
||||
* The base definition string.
|
||||
* @type string
|
||||
*/
|
||||
protected base: string
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Given an incoming route path, returns true if that route matches this route definition.
|
||||
* @param {string} incoming
|
||||
* @return boolean
|
||||
*/
|
||||
public abstract match(incoming: string): boolean
|
||||
|
||||
/**
|
||||
* Given an incoming route path, parse the parameters and return them.
|
||||
* @param {string} incoming
|
||||
* @return RouteParameters
|
||||
*/
|
||||
public abstract build_parameters(incoming: string): RouteParameters
|
||||
|
||||
/**
|
||||
* Get the base definition of this route.
|
||||
*/
|
||||
public get route() {
|
||||
return this.base
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given route string into its segments by '/'.
|
||||
* @param {string} incoming
|
||||
* @return {Array<string>}
|
||||
*/
|
||||
public split(incoming: string) {
|
||||
return incoming.toLowerCase().split('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the incoming route segment and match each segment with the corresponding segment of the definition.
|
||||
* @param {string} incoming
|
||||
* @return ZippedRouteSegments
|
||||
*/
|
||||
public zip(incoming: string) {
|
||||
const incoming_parts: string[] = this.split(incoming)
|
||||
const base_parts: string[] = this.split(this.base)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import {Route, RouteParameters} from './Route.ts'
|
||||
|
||||
/**
|
||||
* A very basic route with no parameters or wild-cards.
|
||||
* @extends Route
|
||||
*/
|
||||
export class SimpleRoute extends Route {
|
||||
public match(incoming: string): boolean {
|
||||
return incoming.toLowerCase() === this.base.toLowerCase()
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import Session from './Session.ts'
|
||||
import SessionInterface, { SessionData } from './SessionInterface.ts'
|
||||
|
||||
/**
|
||||
* Basic session implementation that exists only in memory.
|
||||
* @extends Session
|
||||
* @implements SessionInterface
|
||||
*/
|
||||
export default class MemorySession extends Session implements SessionInterface {
|
||||
private _key!: string
|
||||
private _data: SessionData = {}
|
||||
|
||||
@@ -2,6 +2,10 @@ import SessionFactory from './SessionFactory.ts'
|
||||
import MemorySession from './MemorySession.ts'
|
||||
import SessionInterface from './SessionInterface.ts'
|
||||
|
||||
/**
|
||||
* Session factory that produces memory-based sessions.
|
||||
* @extends SessionFactory
|
||||
*/
|
||||
export default class MemorySessionFactory extends SessionFactory {
|
||||
produce(dependencies: any[], parameters: any[]): SessionInterface {
|
||||
return new MemorySession()
|
||||
|
||||
@@ -5,10 +5,21 @@ import SessionManager, {InvalidSessionKeyError} from './SessionManager.ts'
|
||||
import Utility from '../../service/utility/Utility.ts'
|
||||
import SessionInterface from './SessionInterface.ts'
|
||||
|
||||
/**
|
||||
* Type denoting a memory-stored session.
|
||||
*/
|
||||
export type SessionRegistrant = { key: string, session: SessionInterface }
|
||||
|
||||
/**
|
||||
* Session manager object for memory-based sessions.
|
||||
* @extends SessionManager
|
||||
*/
|
||||
@Service()
|
||||
export default class MemorySessionManager extends SessionManager {
|
||||
/**
|
||||
* Collection of registered, in-memory sessions.
|
||||
* @type Collection<SessionRegistrant>
|
||||
*/
|
||||
private _sessions: Collection<SessionRegistrant> = new Collection<SessionRegistrant>()
|
||||
|
||||
public async has_session(key: string): Promise<boolean> {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import SessionManagerFactory from "./SessionManagerFactory.ts";
|
||||
import MemorySessionManager from "./MemorySessionManager.ts";
|
||||
import SessionManagerFactory from './SessionManagerFactory.ts'
|
||||
import MemorySessionManager from './MemorySessionManager.ts'
|
||||
|
||||
/**
|
||||
* Session manager factory that produces memory-based session managers.
|
||||
* @extends SessionManagerFactory
|
||||
*/
|
||||
export default class MemorySessionManagerFactory extends SessionManagerFactory {
|
||||
produce(dependencies: any[], parameters: any[]): any {
|
||||
return new MemorySessionManager()
|
||||
|
||||
@@ -4,8 +4,16 @@ import {Model} from '../../../../orm/src/model/Model.ts'
|
||||
import {StaticClass} from '../../../../di/src/type/StaticClass.ts'
|
||||
import {isInstantiable} from '../../../../di/src/type/Instantiable.ts'
|
||||
|
||||
/**
|
||||
* Session factory that builds an ORM model-based session factory.
|
||||
* @extends SessionFactory
|
||||
*/
|
||||
export default class ModelSessionFactory extends SessionFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The base model to use for sessions.
|
||||
* @type StaticClass<SessionInterface, typeof Model>
|
||||
*/
|
||||
protected readonly ModelClass: StaticClass<SessionInterface, typeof Model>,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -3,8 +3,16 @@ import {Model} from '../../../../orm/src/model/Model.ts'
|
||||
import SessionInterface, {isSessionInterface} from './SessionInterface.ts'
|
||||
import {StaticClass} from '../../../../di/src/type/StaticClass.ts'
|
||||
|
||||
/**
|
||||
* Session manager that manages sessions using an ORM model.
|
||||
* @extends SessionManager
|
||||
*/
|
||||
export default class ModelSessionManager extends SessionManager {
|
||||
constructor(
|
||||
/**
|
||||
* The base model class to use for session lookups.
|
||||
* @type StaticClass<SessionInterface, typeof Model>
|
||||
*/
|
||||
protected readonly ModelClass: StaticClass<SessionInterface, typeof Model>,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -4,8 +4,16 @@ import {Model} from '../../../../orm/src/model/Model.ts'
|
||||
import {StaticClass} from '../../../../di/src/type/StaticClass.ts'
|
||||
import SessionInterface from './SessionInterface.ts'
|
||||
|
||||
/**
|
||||
* Session manager factory that produces model-based session managers.
|
||||
* @extends SessionManagerFactory
|
||||
*/
|
||||
export default class MemorySessionManagerFactory extends SessionManagerFactory {
|
||||
constructor(
|
||||
/**
|
||||
* The base model class to use for session lookups.
|
||||
* @type StaticClass<SessionInterface, typeof Model>
|
||||
*/
|
||||
protected readonly ModelClass: StaticClass<SessionInterface, typeof Model>,
|
||||
) {
|
||||
super()
|
||||
|
||||
@@ -1,13 +1,59 @@
|
||||
import AppClass from '../../lifecycle/AppClass.ts'
|
||||
import SessionInterface, {SessionData} from './SessionInterface.ts'
|
||||
|
||||
/**
|
||||
* Abstract base-class for the request's session.
|
||||
* @extends AppClass
|
||||
* @implements SessionInterface
|
||||
*/
|
||||
export default abstract class Session extends AppClass implements SessionInterface {
|
||||
/**
|
||||
* Get the unique identifier for this session.
|
||||
* @return string
|
||||
*/
|
||||
public abstract get_key(): string
|
||||
|
||||
/**
|
||||
* Set the unique identifier for this session.
|
||||
* @param {string} key
|
||||
*/
|
||||
public abstract set_key(key: string): void
|
||||
|
||||
/**
|
||||
* Persist the session to its storage backend.
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public abstract async persist(): Promise<void>
|
||||
|
||||
/**
|
||||
* Get the session data.
|
||||
* @return SessionData
|
||||
*/
|
||||
public abstract get_data(): SessionData
|
||||
|
||||
/**
|
||||
* Set the session data.
|
||||
* @param {SessionData} data
|
||||
*/
|
||||
public abstract set_data(data: SessionData): void
|
||||
|
||||
/**
|
||||
* Get the session attribute by key.
|
||||
* @param {string} key
|
||||
* @return any
|
||||
*/
|
||||
public abstract get_attribute(key: string): any
|
||||
|
||||
/**
|
||||
* Set the session attribute by key.
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
*/
|
||||
public abstract set_attribute(key: string, value: any): void
|
||||
|
||||
/**
|
||||
* Initialize the session in its backend.
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public abstract async init_session(): Promise<void>
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import {DependencyRequirement} from '../../../../di/src/type/DependencyRequireme
|
||||
import {Collection} from '../../collection/Collection.ts'
|
||||
import SessionInterface from './SessionInterface.ts'
|
||||
|
||||
// TODO support configurable session backends
|
||||
|
||||
/**
|
||||
* Base class for IoC container factories that produce sessions.
|
||||
* @extends AbstractFactory
|
||||
*/
|
||||
export default class SessionFactory extends AbstractFactory {
|
||||
constructor() {
|
||||
super({})
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import {logger} from "../../service/logging/global.ts";
|
||||
import {logger} from '../../service/logging/global.ts'
|
||||
|
||||
/**
|
||||
* Base type for session data.
|
||||
*/
|
||||
export type SessionData = { [key: string]: any }
|
||||
|
||||
/**
|
||||
* Base type for the abstract session interface.
|
||||
*/
|
||||
export default interface SessionInterface {
|
||||
get_key(): string
|
||||
set_key(key: string): void
|
||||
@@ -13,6 +19,11 @@ export default interface SessionInterface {
|
||||
init_session(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given object is a valid session.
|
||||
* @param what
|
||||
* @return boolean
|
||||
*/
|
||||
export function isSessionInterface(what: any): what is SessionInterface {
|
||||
const name_length_checks = [
|
||||
{ name: 'get_key', length: 0 },
|
||||
|
||||
@@ -1,16 +1,41 @@
|
||||
import AppClass from '../../lifecycle/AppClass.ts'
|
||||
import SessionInterface from './SessionInterface.ts'
|
||||
|
||||
/**
|
||||
* Error thrown if a session is looked up using a key that doesn't exist.
|
||||
* @extends Error
|
||||
*/
|
||||
export class InvalidSessionKeyError extends Error {
|
||||
constructor(key: any) {
|
||||
super(`Invalid session key: ${key}. No session exists.`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class for managing sessions.
|
||||
* @extends AppClass
|
||||
*/
|
||||
export default abstract class SessionManager extends AppClass {
|
||||
|
||||
/**
|
||||
* Attempt to find a session by key if it exists, or create one if no key is provided.
|
||||
* @param {string} [key]
|
||||
* @return Promise<SessionInterface>
|
||||
*/
|
||||
public abstract async get_session(key?: string): Promise<SessionInterface>
|
||||
|
||||
/**
|
||||
* Returns true if the manager has a session with the given key.
|
||||
* @param {string} key
|
||||
* @return Promise<boolean>
|
||||
*/
|
||||
public abstract async has_session(key: string): Promise<boolean>
|
||||
|
||||
/**
|
||||
* Purge a session by key, if provided, or all sessions.
|
||||
* @param {string} key
|
||||
* @return Promise<void>
|
||||
*/
|
||||
public abstract async purge(key?: string): Promise<void>
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import {Collection} from '../../collection/Collection.ts'
|
||||
import MemorySessionManager from './MemorySessionManager.ts'
|
||||
import SessionManager from './SessionManager.ts'
|
||||
|
||||
// TODO support configurable session backends
|
||||
|
||||
/**
|
||||
* Base class for IoC factories that produce session managers.
|
||||
* @extends AbstractFactory
|
||||
*/
|
||||
export default class SessionManagerFactory extends AbstractFactory {
|
||||
constructor() {
|
||||
super({})
|
||||
|
||||
@@ -3,9 +3,18 @@ import SessionInterface, {SessionData} from './SessionInterface.ts'
|
||||
import {Field} from '../../../../orm/src/model/Field.ts'
|
||||
import {Type} from '../../../../orm/src/db/types.ts'
|
||||
|
||||
/**
|
||||
* Base class for an ORM session model.
|
||||
* @extends Model<SessionModel>
|
||||
* @implements SessionInterface
|
||||
*/
|
||||
export default class SessionModel extends Model<SessionModel> implements SessionInterface {
|
||||
protected static populate_key_on_insert: boolean = true
|
||||
|
||||
/**
|
||||
* The JSON serialized session data.
|
||||
* @type string
|
||||
*/
|
||||
@Field(Type.json)
|
||||
protected data?: string
|
||||
|
||||
|
||||
@@ -3,18 +3,27 @@ import {HTTPResponse} from './HTTPResponse.ts'
|
||||
import SessionInterface from '../session/SessionInterface.ts'
|
||||
import ActivatedRoute from '../routing/ActivatedRoute.ts'
|
||||
|
||||
/**
|
||||
* Base type representing an HTTP protocol version.
|
||||
*/
|
||||
export interface HTTPProtocol {
|
||||
string: string,
|
||||
major: number,
|
||||
minor: number,
|
||||
}
|
||||
|
||||
/**
|
||||
* Base type representing a remote host.
|
||||
*/
|
||||
export interface RemoteHost {
|
||||
hostname: string,
|
||||
port: number,
|
||||
transport: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Base type for an incoming HTTP request.
|
||||
*/
|
||||
export interface HTTPRequest {
|
||||
url: string
|
||||
method: string
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {CookieJar} from '../CookieJar.ts'
|
||||
|
||||
/**
|
||||
* Base type for an outgoing HTTP response.
|
||||
*/
|
||||
export interface HTTPResponse {
|
||||
status: number
|
||||
headers: Headers
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import {logger} from '../../service/logging/global.ts'
|
||||
|
||||
/**
|
||||
* Type representing valid HTTP verbs.
|
||||
*/
|
||||
export type RouteVerb = 'get' | 'post' | 'patch' | 'delete' | 'head' | 'put' | 'connect' | 'options' | 'trace'
|
||||
|
||||
/**
|
||||
* Type representing a route verb group from a router definition.
|
||||
*/
|
||||
export type RouteVerbGroup = { [key: string]: string | string[] }
|
||||
|
||||
/**
|
||||
* Type representing a router definition.
|
||||
*/
|
||||
export interface RouterDefinition {
|
||||
prefix?: string,
|
||||
middleware?: string[],
|
||||
@@ -17,11 +27,21 @@ export interface RouterDefinition {
|
||||
trace?: RouteVerbGroup,
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given value is a valid HTTP verb.
|
||||
* @param something
|
||||
* @return boolean
|
||||
*/
|
||||
export function isRouteVerb(something: any): something is RouteVerb {
|
||||
const route_verbs = ['get', 'post', 'patch', 'delete', 'head', 'put', 'connect', 'options', 'trace']
|
||||
return route_verbs.includes(something)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given value is a valid route verb group definition.
|
||||
* @param something
|
||||
* @return boolean
|
||||
*/
|
||||
export function isRouteVerbGroup(something: any): something is RouteVerbGroup {
|
||||
if ( !(typeof something === 'object' ) ) return false
|
||||
for ( const key in something ) {
|
||||
@@ -41,6 +61,11 @@ export function isRouteVerbGroup(something: any): something is RouteVerbGroup {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given value is a valid router definition.
|
||||
* @param something
|
||||
* @return boolean
|
||||
*/
|
||||
export function isRouterDefinition(something: any): something is RouterDefinition {
|
||||
if ( !(typeof something === 'object') ) {
|
||||
logger.debug('Routing definition is not an object.')
|
||||
|
||||
Reference in New Issue
Block a user