Start routing; rehydratable interface; add verbose logging
This commit is contained in:
parent
a6995c6a85
commit
a27618d5a3
@ -6,3 +6,4 @@ export { default as RoutesUnit } from '../../lib/src/unit/Routes.ts'
|
|||||||
export { default as HttpKernelUnit } from '../../lib/src/unit/HttpKernel.ts'
|
export { default as HttpKernelUnit } from '../../lib/src/unit/HttpKernel.ts'
|
||||||
export { default as ModelsUnit } from '../../orm/src/ModelsUnit.ts'
|
export { default as ModelsUnit } from '../../orm/src/ModelsUnit.ts'
|
||||||
export { default as HttpServerUnit } from '../../lib/src/unit/HttpServer.ts'
|
export { default as HttpServerUnit } from '../../lib/src/unit/HttpServer.ts'
|
||||||
|
export { default as RoutingUnit } from '../../lib/src/unit/Routing.ts'
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export default {
|
export default {
|
||||||
port: 8080,
|
port: 8080,
|
||||||
use_ssl: false,
|
use_ssl: false,
|
||||||
|
prefix: '/',
|
||||||
|
allow_mount_without_prefix: false,
|
||||||
|
|
||||||
powered_by: {
|
powered_by: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
11
app/units.ts
11
app/units.ts
@ -1,5 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
ConfigUnit, DatabaseUnit, ControllerUnit, MiddlewareUnit, RoutesUnit, HttpKernelUnit, ModelsUnit, HttpServerUnit
|
ConfigUnit,
|
||||||
|
DatabaseUnit,
|
||||||
|
ControllerUnit,
|
||||||
|
MiddlewareUnit,
|
||||||
|
RoutesUnit,
|
||||||
|
HttpKernelUnit,
|
||||||
|
ModelsUnit,
|
||||||
|
HttpServerUnit,
|
||||||
|
RoutingUnit
|
||||||
} from './bundle/daton_units.ts'
|
} from './bundle/daton_units.ts'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
@ -10,5 +18,6 @@ export default [
|
|||||||
MiddlewareUnit,
|
MiddlewareUnit,
|
||||||
ControllerUnit,
|
ControllerUnit,
|
||||||
RoutesUnit,
|
RoutesUnit,
|
||||||
|
RoutingUnit,
|
||||||
HttpServerUnit,
|
HttpServerUnit,
|
||||||
]
|
]
|
||||||
|
@ -5,6 +5,7 @@ import { HTTPResponse } from './type/HTTPResponse.ts'
|
|||||||
import Utility from '../service/utility/Utility.ts'
|
import Utility from '../service/utility/Utility.ts'
|
||||||
import { Injectable } from '../../../di/src/decorator/Injection.ts'
|
import { Injectable } from '../../../di/src/decorator/Injection.ts'
|
||||||
import SessionInterface from './session/SessionInterface.ts'
|
import SessionInterface from './session/SessionInterface.ts'
|
||||||
|
import ActivatedRoute from './routing/ActivatedRoute.ts'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Request implements HTTPRequest {
|
export class Request implements HTTPRequest {
|
||||||
@ -13,6 +14,7 @@ export class Request implements HTTPRequest {
|
|||||||
private _body: any
|
private _body: any
|
||||||
private _query: { [key: string]: any } = {}
|
private _query: { [key: string]: any } = {}
|
||||||
private _session!: SessionInterface
|
private _session!: SessionInterface
|
||||||
|
private _activated_route!: ActivatedRoute
|
||||||
|
|
||||||
public readonly url: string
|
public readonly url: string
|
||||||
public readonly method: string
|
public readonly method: string
|
||||||
@ -37,9 +39,19 @@ export class Request implements HTTPRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set session(session: SessionInterface) {
|
set session(session: SessionInterface) {
|
||||||
|
if ( !this._session )
|
||||||
this._session = session
|
this._session = session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get route(): ActivatedRoute {
|
||||||
|
return this._activated_route
|
||||||
|
}
|
||||||
|
|
||||||
|
set route(route: ActivatedRoute) {
|
||||||
|
if ( !this._activated_route )
|
||||||
|
this._activated_route = route
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected utility: Utility,
|
protected utility: Utility,
|
||||||
from: ServerRequest
|
from: ServerRequest
|
||||||
@ -95,7 +107,7 @@ export class Request implements HTTPRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get path() {
|
get path() {
|
||||||
return this.url.split('?')[0]
|
return this.url.split('?')[0].split('#')[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
get xhr() {
|
get xhr() {
|
||||||
|
@ -4,6 +4,7 @@ import AppClass from '../../lifecycle/AppClass.ts'
|
|||||||
import {Collection} from '../../collection/Collection.ts'
|
import {Collection} from '../../collection/Collection.ts'
|
||||||
import {Service} from '../../../../di/src/decorator/Service.ts'
|
import {Service} from '../../../../di/src/decorator/Service.ts'
|
||||||
import {Request} from '../Request.ts'
|
import {Request} from '../Request.ts'
|
||||||
|
import {Logging} from '../../service/logging/Logging.ts'
|
||||||
|
|
||||||
export interface ModuleRegistrationFluency {
|
export interface ModuleRegistrationFluency {
|
||||||
before: (other?: Instantiable<Module>) => Kernel,
|
before: (other?: Instantiable<Module>) => Kernel,
|
||||||
@ -42,6 +43,7 @@ export default class Kernel extends AppClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public register(module: Instantiable<Module>): ModuleRegistrationFluency {
|
public register(module: Instantiable<Module>): ModuleRegistrationFluency {
|
||||||
|
this.make(Logging).verbose(`Registering HTTP kernel module: ${module.name}`)
|
||||||
return {
|
return {
|
||||||
before: (other?: Instantiable<Module>): Kernel => {
|
before: (other?: Instantiable<Module>): Kernel => {
|
||||||
if ( !other ) {
|
if ( !other ) {
|
||||||
|
53
lib/src/http/kernel/module/MountActivatedRoute.ts
Normal file
53
lib/src/http/kernel/module/MountActivatedRoute.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import Module from '../Module.ts'
|
||||||
|
import Kernel from '../Kernel.ts'
|
||||||
|
import PrepareRequest from './PrepareRequest.ts'
|
||||||
|
import Routing from '../../../unit/Routing.ts'
|
||||||
|
import {Logging} from '../../../service/logging/Logging.ts'
|
||||||
|
import {Request} from '../../Request.ts'
|
||||||
|
import Config from '../../../unit/Config.ts'
|
||||||
|
import ActivatedRoute from '../../routing/ActivatedRoute.ts'
|
||||||
|
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class MountActivatedRoute extends Module {
|
||||||
|
public static register(kernel: Kernel) {
|
||||||
|
kernel.register(this).after(PrepareRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly routing: Routing,
|
||||||
|
protected readonly config: Config,
|
||||||
|
protected readonly logger: Logging,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async apply(request: Request): Promise<Request> {
|
||||||
|
let incoming = this.routing.resolve([request.path])
|
||||||
|
this.logger.info(`${request.method} ${incoming}`)
|
||||||
|
|
||||||
|
const prefix: string | undefined = this.config.get('server.prefix')
|
||||||
|
const allow_mount_without_prefix: boolean = this.config.get('server.allow_mount_without_prefix')
|
||||||
|
|
||||||
|
if ( prefix && this.routing.resolve([prefix]) !== '/' ) {
|
||||||
|
if ( incoming.startsWith(prefix) ) {
|
||||||
|
incoming = this.routing.resolve([incoming.replace(prefix, '')])
|
||||||
|
this.logger.verbose(`Resolved prefix: ${incoming}`)
|
||||||
|
} else if ( !allow_mount_without_prefix ) {
|
||||||
|
this.logger.warn(`Will not attempt to mount route with missing prefix: ${incoming}`)
|
||||||
|
this.logger.debug('To allow this, set the config server.allow_mount_without_prefix = true.')
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activated_route: ActivatedRoute | undefined = this.routing.build(incoming)
|
||||||
|
if ( activated_route ) {
|
||||||
|
this.logger.verbose(`Resolved activated route: ${activated_route.route.route}`)
|
||||||
|
request.route = activated_route
|
||||||
|
} else {
|
||||||
|
this.logger.verbose(`Unable to resolve route to activated route. No matching route was found.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
16
lib/src/http/response/DehydratedStateResponseFactory.ts
Normal file
16
lib/src/http/response/DehydratedStateResponseFactory.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import ResponseFactory from './ResponseFactory.ts'
|
||||||
|
import {Rehydratable} from '../../support/Rehydratable.ts'
|
||||||
|
import {Request} from '../Request.ts'
|
||||||
|
|
||||||
|
export default class DehydratedStateResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly rehydratable: Rehydratable
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(request: Request): Promise<Request> {
|
||||||
|
request.response.body = JSON.stringify(this.rehydratable.dehydrate())
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
15
lib/src/http/response/JSONResponseFactory.ts
Normal file
15
lib/src/http/response/JSONResponseFactory.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import ResponseFactory from './ResponseFactory.ts'
|
||||||
|
import {Request} from '../Request.ts'
|
||||||
|
|
||||||
|
export default class JSONResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly value: any
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(request: Request): Promise<Request> {
|
||||||
|
request.response.body = JSON.stringify(this.value)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
6
lib/src/http/response/ResponseFactory.ts
Normal file
6
lib/src/http/response/ResponseFactory.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import AppClass from '../../lifecycle/AppClass.ts'
|
||||||
|
import {Request} from '../Request.ts'
|
||||||
|
|
||||||
|
export default abstract class ResponseFactory extends AppClass {
|
||||||
|
public abstract async write(request: Request): Promise<Request>
|
||||||
|
}
|
15
lib/src/http/response/StringResponseFactory.ts
Normal file
15
lib/src/http/response/StringResponseFactory.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import ResponseFactory from './ResponseFactory.ts'
|
||||||
|
import {Request} from '../Request.ts'
|
||||||
|
|
||||||
|
export default class StringResponseFactory extends ResponseFactory {
|
||||||
|
constructor(
|
||||||
|
public readonly value: string,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async write(request: Request): Promise<Request> {
|
||||||
|
request.response.body = this.value
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
14
lib/src/http/routing/ActivatedRoute.ts
Normal file
14
lib/src/http/routing/ActivatedRoute.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import AppClass from '../../lifecycle/AppClass.ts'
|
||||||
|
import {Route, RouteParameters} from './Route.ts'
|
||||||
|
|
||||||
|
export default class ActivatedRoute extends AppClass {
|
||||||
|
public readonly params: RouteParameters
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly incoming: string,
|
||||||
|
public readonly route: Route,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.params = route.build_parameters(incoming)
|
||||||
|
}
|
||||||
|
}
|
29
lib/src/http/routing/ComplexRoute.ts
Normal file
29
lib/src/http/routing/ComplexRoute.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {Route, RouteParameters, RouteSegment} from './Route.ts'
|
||||||
|
import Utility from '../../service/utility/Utility.ts'
|
||||||
|
import {make} from '../../../../di/src/global.ts'
|
||||||
|
|
||||||
|
export class ComplexRoute extends Route {
|
||||||
|
public match(incoming: string): boolean {
|
||||||
|
const base_length = this.split(this.base).length
|
||||||
|
const incoming_length = this.split(incoming).length
|
||||||
|
return base_length === incoming_length // FIXME match!
|
||||||
|
}
|
||||||
|
|
||||||
|
public build_parameters(incoming: string): RouteParameters {
|
||||||
|
const utility = make(Utility)
|
||||||
|
const params: RouteParameters = {}
|
||||||
|
let current_wildcard: number = 1
|
||||||
|
|
||||||
|
this.zip(incoming).forEach((segment: RouteSegment) => {
|
||||||
|
if ( segment.base.indexOf('*') >= 0 ) {
|
||||||
|
params[`$${current_wildcard}`] = utility.infer(segment.match)
|
||||||
|
current_wildcard += 1
|
||||||
|
} else if ( segment.base.startsWith(':') && segment.base.length > 1 ) {
|
||||||
|
const name = segment.base.substr(1)
|
||||||
|
params[name] = utility.infer(segment.match)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
41
lib/src/http/routing/Route.ts
Normal file
41
lib/src/http/routing/Route.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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[]
|
||||||
|
|
||||||
|
export abstract class Route {
|
||||||
|
constructor(
|
||||||
|
protected base: string
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public abstract match(incoming: string): boolean
|
||||||
|
public abstract build_parameters(incoming: string): RouteParameters
|
||||||
|
|
||||||
|
public get route() {
|
||||||
|
return this.base
|
||||||
|
}
|
||||||
|
|
||||||
|
public split(incoming: string) {
|
||||||
|
return incoming.toLowerCase().split('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
public zip(incoming: string) {
|
||||||
|
const incoming_parts: string[] = this.split(incoming)
|
||||||
|
const base_parts: string[] = this.split(this.base)
|
||||||
|
const zipped_parts: ZippedRouteSegments = []
|
||||||
|
|
||||||
|
if ( incoming_parts.length !== base_parts.length ) {
|
||||||
|
logger.warn(`Zipping routes with different part lengths! (Base: ${this.base}, Incoming: ${incoming}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
base_parts.forEach((part, index) => {
|
||||||
|
zipped_parts.push({
|
||||||
|
base: part,
|
||||||
|
match: (index >= incoming_parts.length ? undefined : incoming_parts[index]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return zipped_parts
|
||||||
|
}
|
||||||
|
}
|
11
lib/src/http/routing/SimpleRoute.ts
Normal file
11
lib/src/http/routing/SimpleRoute.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {Route, RouteParameters} from './Route.ts'
|
||||||
|
|
||||||
|
export class SimpleRoute extends Route {
|
||||||
|
public match(incoming: string): boolean {
|
||||||
|
return incoming === this.base
|
||||||
|
}
|
||||||
|
|
||||||
|
public build_parameters(incoming: string): RouteParameters {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { ServerRequest } from '../../external/http.ts'
|
import { ServerRequest } from '../../external/http.ts'
|
||||||
import {HTTPResponse} from './HTTPResponse.ts'
|
import {HTTPResponse} from './HTTPResponse.ts'
|
||||||
import SessionInterface from '../session/SessionInterface.ts'
|
import SessionInterface from '../session/SessionInterface.ts'
|
||||||
|
import ActivatedRoute from '../routing/ActivatedRoute.ts'
|
||||||
|
|
||||||
export interface HTTPProtocol {
|
export interface HTTPProtocol {
|
||||||
string: string,
|
string: string,
|
||||||
@ -30,4 +31,5 @@ export interface HTTPRequest {
|
|||||||
secure: boolean
|
secure: boolean
|
||||||
|
|
||||||
session: SessionInterface,
|
session: SessionInterface,
|
||||||
|
route?: ActivatedRoute,
|
||||||
}
|
}
|
||||||
|
16
lib/src/support/Rehydratable.ts
Normal file
16
lib/src/support/Rehydratable.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
export type JSONState = { [key: string]: string | boolean | number | JSONState | Array<string | boolean | number | JSONState> }
|
||||||
|
|
||||||
|
export function isJSONState(what: any): what is JSONState {
|
||||||
|
try {
|
||||||
|
JSON.stringify(what)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rehydratable {
|
||||||
|
dehydrate(): Promise<JSONState>
|
||||||
|
rehydrate(state: JSONState): void | Promise<void>
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import LifecycleUnit from '../lifecycle/Unit.ts'
|
import LifecycleUnit from '../lifecycle/Unit.ts'
|
||||||
import {fs, path} from '../external/std.ts'
|
import {fs, path} from '../external/std.ts'
|
||||||
import {Canon} from './Canon.ts'
|
import {Canon} from './Canon.ts'
|
||||||
|
import {Logging} from '../service/logging/Logging.ts'
|
||||||
|
|
||||||
export interface CanonicalDefinition {
|
export interface CanonicalDefinition {
|
||||||
canonical_name: string,
|
canonical_name: string,
|
||||||
@ -14,6 +15,10 @@ export class Canonical<T> extends LifecycleUnit {
|
|||||||
protected canonical_item: string = ''
|
protected canonical_item: string = ''
|
||||||
protected _items: { [key: string]: T } = {}
|
protected _items: { [key: string]: T } = {}
|
||||||
|
|
||||||
|
public all(): string[] {
|
||||||
|
return Object.keys(this._items)
|
||||||
|
}
|
||||||
|
|
||||||
public get path(): string {
|
public get path(): string {
|
||||||
return path.resolve(this.base_path)
|
return path.resolve(this.base_path)
|
||||||
}
|
}
|
||||||
@ -23,9 +28,14 @@ export class Canonical<T> extends LifecycleUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async up() {
|
public async up() {
|
||||||
|
const logger = this.make(Logging)
|
||||||
for await ( const entry of fs.walk(this.path) ) {
|
for await ( const entry of fs.walk(this.path) ) {
|
||||||
if ( !entry.isFile || !entry.path.endsWith(this.suffix) ) continue
|
if ( !entry.isFile || !entry.path.endsWith(this.suffix) ) {
|
||||||
|
if ( entry.isFile ) logger.debug(`Skipping file in canonical path with invalid suffix: ${entry.path}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
const def = await this._get_canonical_definition(entry.path)
|
const def = await this._get_canonical_definition(entry.path)
|
||||||
|
logger.verbose(`Registering canonical ${this.canonical_item} "${def.canonical_name}" from ${entry.path}.`)
|
||||||
this._items[def.canonical_name] = await this.init_canonical_item(def)
|
this._items[def.canonical_name] = await this.init_canonical_item(def)
|
||||||
}
|
}
|
||||||
this.make(Canon).register_canonical(this)
|
this.make(Canon).register_canonical(this)
|
||||||
|
@ -17,6 +17,7 @@ import ModelSessionManagerFactory from '../http/session/ModelSessionManagerFacto
|
|||||||
import SessionInterface from '../http/session/SessionInterface.ts'
|
import SessionInterface from '../http/session/SessionInterface.ts'
|
||||||
import InjectSession from '../http/kernel/module/InjectSession.ts'
|
import InjectSession from '../http/kernel/module/InjectSession.ts'
|
||||||
import PersistSession from '../http/kernel/module/PersistSession.ts'
|
import PersistSession from '../http/kernel/module/PersistSession.ts'
|
||||||
|
import MountActivatedRoute from '../http/kernel/module/MountActivatedRoute.ts'
|
||||||
|
|
||||||
@Unit()
|
@Unit()
|
||||||
export default class HttpKernel extends LifecycleUnit {
|
export default class HttpKernel extends LifecycleUnit {
|
||||||
@ -32,15 +33,7 @@ export default class HttpKernel extends LifecycleUnit {
|
|||||||
|
|
||||||
public async up() {
|
public async up() {
|
||||||
this.determine_session_provider()
|
this.determine_session_provider()
|
||||||
|
this.register_modules()
|
||||||
PrepareRequest.register(this.kernel)
|
|
||||||
SetSessionCookie.register(this.kernel)
|
|
||||||
InjectSession.register(this.kernel)
|
|
||||||
PersistSession.register(this.kernel)
|
|
||||||
|
|
||||||
if ( this.config.get('server.powered_by.enable') ) {
|
|
||||||
SetDatonHeaders.register(this.kernel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected determine_session_provider() {
|
protected determine_session_provider() {
|
||||||
@ -68,4 +61,16 @@ export default class HttpKernel extends LifecycleUnit {
|
|||||||
this.injector.register_factory(new ModelSessionManagerFactory(ModelClass))
|
this.injector.register_factory(new ModelSessionManagerFactory(ModelClass))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected register_modules() {
|
||||||
|
PrepareRequest.register(this.kernel)
|
||||||
|
SetSessionCookie.register(this.kernel)
|
||||||
|
InjectSession.register(this.kernel)
|
||||||
|
PersistSession.register(this.kernel)
|
||||||
|
MountActivatedRoute.register(this.kernel)
|
||||||
|
|
||||||
|
if ( this.config.get('server.powered_by.enable') ) {
|
||||||
|
SetDatonHeaders.register(this.kernel)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,11 @@ export default class HttpServer extends LifecycleUnit {
|
|||||||
|
|
||||||
public async up() {
|
public async up() {
|
||||||
this._server = serve({ port: 8000 })
|
this._server = serve({ port: 8000 })
|
||||||
|
|
||||||
this.logger.success(`HTTP/S server listening on port 8000!`)
|
this.logger.success(`HTTP/S server listening on port 8000!`)
|
||||||
|
|
||||||
for await ( const native_request of this._server ) {
|
for await ( const native_request of this._server ) {
|
||||||
let req: Request = this.make(Request, native_request)
|
let req: Request = this.make(Request, native_request)
|
||||||
req = await this.kernel.handle(req)
|
req = await this.kernel.handle(req)
|
||||||
|
|
||||||
req.response.body = req.session.get_key()
|
|
||||||
req.response.send()
|
req.response.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import {Canonical, CanonicalDefinition} from './Canonical.ts'
|
import {Canonical, CanonicalDefinition} from './Canonical.ts'
|
||||||
import {isRouterDefinition, RouterDefinition} from '../http/type/RouterDefinition.ts'
|
import {isRouterDefinition, RouterDefinition} from '../http/type/RouterDefinition.ts'
|
||||||
|
import {Unit} from '../lifecycle/decorators.ts'
|
||||||
|
|
||||||
|
@Unit()
|
||||||
export default class Routes extends Canonical<RouterDefinition> {
|
export default class Routes extends Canonical<RouterDefinition> {
|
||||||
protected base_path = './app/http/routes'
|
protected base_path = './app/http/routes'
|
||||||
protected canonical_item = 'route_group'
|
protected canonical_item = 'route_group'
|
||||||
|
105
lib/src/unit/Routing.ts
Normal file
105
lib/src/unit/Routing.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import LifecycleUnit from '../lifecycle/Unit.ts'
|
||||||
|
import {Unit} from '../lifecycle/decorators.ts'
|
||||||
|
import Routes from './Routes.ts'
|
||||||
|
import {RouterDefinition} from '../http/type/RouterDefinition.ts'
|
||||||
|
import {Collection} from '../collection/Collection.ts'
|
||||||
|
import {Route} from '../http/routing/Route.ts'
|
||||||
|
import {SimpleRoute} from "../http/routing/SimpleRoute.ts";
|
||||||
|
import {ComplexRoute} from "../http/routing/ComplexRoute.ts";
|
||||||
|
import ActivatedRoute from "../http/routing/ActivatedRoute.ts";
|
||||||
|
|
||||||
|
export type RouteHandler = () => any
|
||||||
|
export interface RouteDefinition {
|
||||||
|
get?: RouteHandler,
|
||||||
|
post?: RouteHandler,
|
||||||
|
patch?: RouteHandler,
|
||||||
|
delete?: RouteHandler,
|
||||||
|
head?: RouteHandler,
|
||||||
|
put?: RouteHandler,
|
||||||
|
connect?: RouteHandler,
|
||||||
|
options?: RouteHandler,
|
||||||
|
trace?: RouteHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
const verbs = ['get', 'post', 'patch', 'delete', 'head', 'put', 'connect', 'options', 'trace']
|
||||||
|
|
||||||
|
@Unit()
|
||||||
|
export default class Routing extends LifecycleUnit {
|
||||||
|
protected definitions: { [key: string]: RouteDefinition } = {}
|
||||||
|
protected instances: Collection<Route> = new Collection<Route>()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly routes: Routes,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
const route_groups = this.routes.all()
|
||||||
|
for ( const route_group_name of route_groups ) {
|
||||||
|
const route_group: RouterDefinition | undefined = this.routes.get(route_group_name)
|
||||||
|
if ( !route_group ) continue
|
||||||
|
|
||||||
|
const prefix = route_group.prefix || '/'
|
||||||
|
for ( const verb of verbs ) {
|
||||||
|
// @ts-ignore
|
||||||
|
if ( route_group[verb] ) {
|
||||||
|
// @ts-ignore
|
||||||
|
const group = route_group[verb]
|
||||||
|
for ( const key in group ) {
|
||||||
|
if ( !group.hasOwnProperty(key) ) continue
|
||||||
|
const handlers = Array.isArray(group[key]) ? group[key] : [group[key]]
|
||||||
|
const base = this.resolve([prefix, key])
|
||||||
|
|
||||||
|
if ( !this.definitions[base] ) {
|
||||||
|
this.definitions[base] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.definitions[base][verb] = this.build_handler(handlers) // TODO want to rework this
|
||||||
|
this.instances.push(this.build_route(base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public build_handler(group: string[]): () => any {
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(parts: string[]): string {
|
||||||
|
const cleaned = parts.map(part => {
|
||||||
|
if ( part.startsWith('/') ) part = part.substr(1)
|
||||||
|
if ( part.endsWith('/') ) part = part.slice(0, -1)
|
||||||
|
return part
|
||||||
|
})
|
||||||
|
|
||||||
|
let joined = cleaned.join('/')
|
||||||
|
if ( joined.startsWith('/') ) joined = joined.substr(1)
|
||||||
|
if ( joined.endsWith('/') ) joined = joined.slice(0, -1)
|
||||||
|
return `/${joined}`.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
public build_route(base: string): Route {
|
||||||
|
if ( !base.includes(':') && !base.includes('*') ) {
|
||||||
|
return new SimpleRoute(base)
|
||||||
|
} else {
|
||||||
|
return new ComplexRoute(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO deep-match route
|
||||||
|
}
|
||||||
|
|
||||||
|
public match(incoming: string): Route | undefined {
|
||||||
|
return this.instances.firstWhere((route: Route) => route.match(incoming))
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(incoming: string): ActivatedRoute | undefined {
|
||||||
|
const route: Route | undefined = this.match(incoming)
|
||||||
|
if ( route ) {
|
||||||
|
return new ActivatedRoute(incoming, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import {logger} from '../../../lib/src/service/logging/global.ts'
|
|||||||
import {QueryFilter} from './filter.ts'
|
import {QueryFilter} from './filter.ts'
|
||||||
import {BehaviorSubject} from '../../../lib/src/support/BehaviorSubject.ts'
|
import {BehaviorSubject} from '../../../lib/src/support/BehaviorSubject.ts'
|
||||||
import {Scope} from '../builder/Scope.ts'
|
import {Scope} from '../builder/Scope.ts'
|
||||||
|
import {JSONState, Rehydratable} from '../../../lib/src/support/Rehydratable.ts'
|
||||||
|
|
||||||
// TODO separate read/write connections
|
// TODO separate read/write connections
|
||||||
// TODO manual dirty flags
|
// TODO manual dirty flags
|
||||||
@ -19,7 +20,7 @@ import {Scope} from '../builder/Scope.ts'
|
|||||||
* Base class for database models.
|
* Base class for database models.
|
||||||
* @extends Builder
|
* @extends Builder
|
||||||
*/
|
*/
|
||||||
export abstract class Model<T extends Model<T>> extends Builder<T> {
|
export abstract class Model<T extends Model<T>> extends Builder<T> implements Rehydratable {
|
||||||
/**
|
/**
|
||||||
* The name of the connection this model should run through.
|
* The name of the connection this model should run through.
|
||||||
* @type string
|
* @type string
|
||||||
@ -837,4 +838,19 @@ export abstract class Model<T extends Model<T>> extends Builder<T> {
|
|||||||
public static qualified_key_name() {
|
public static qualified_key_name() {
|
||||||
return `${this.table_name()}.${this.key}`
|
return `${this.table_name()}.${this.key}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dehydrate the model. Implements Rehydratable interface.
|
||||||
|
*/
|
||||||
|
public async dehydrate(): Promise<JSONState> {
|
||||||
|
return this.to_object()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rehydrate the model. Implements Rehydratable interface.
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
public async rehydrate(state: JSONState) {
|
||||||
|
this.assume_from_source(state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user