Comparing version 0.1.0-alpha.b123904d to 0.1.0-alpha.caa0578b
@@ -0,3 +1,6 @@ | ||
/// <reference types="koa__cors" /> | ||
import Cors from "@koa/cors"; | ||
import Koa, { Context } from "koa"; | ||
import { ActionResult, Application, Configuration, Facility, Invocation, KoaMiddleware, PlumierApplication, PlumierConfiguration, Middleware } from "./framework"; | ||
import { ActionResult, Application, BodyParserOption, Configuration, Facility, Invocation, KoaMiddleware, Middleware, PlumierApplication, PlumierConfiguration, RouteInfo } from "./core"; | ||
export declare function extractDecorators(route: RouteInfo): Middleware[]; | ||
export declare class MiddlewareInvocation implements Invocation { | ||
@@ -16,2 +19,29 @@ private middleware; | ||
export declare function pipe(middleware: Middleware[], context: Context, invocation: Invocation): Invocation; | ||
/** | ||
* Preset configuration for building web api. This facility contains: | ||
* | ||
* body parser: koa-bodyparser | ||
* | ||
* cors: @koa/cors | ||
*/ | ||
export declare class WebApiFacility implements Facility { | ||
private opt?; | ||
constructor(opt?: { | ||
bodyParser?: BodyParserOption | undefined; | ||
cors?: Cors.Options | undefined; | ||
} | undefined); | ||
setup(app: Readonly<PlumierApplication>): Promise<void>; | ||
} | ||
/** | ||
* Preset configuration for building restful style api. This facility contains: | ||
* | ||
* body parser: koa-bodyparser | ||
* | ||
* cors: @koa/cors | ||
* | ||
* default response status: { get: 200, post: 201, put: 204, delete: 204 } | ||
*/ | ||
export declare class RestfulApiFacility extends WebApiFacility { | ||
setup(app: Readonly<PlumierApplication>): Promise<void>; | ||
} | ||
export declare class Plumier implements PlumierApplication { | ||
@@ -18,0 +48,0 @@ readonly config: Readonly<PlumierConfiguration>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const tslib_1 = require("tslib"); | ||
const cors_1 = tslib_1.__importDefault(require("@koa/cors")); | ||
const debug_1 = tslib_1.__importDefault(require("debug")); | ||
const fs_1 = require("fs"); | ||
const koa_1 = tslib_1.__importDefault(require("koa")); | ||
const path_1 = require("path"); | ||
const util_1 = require("util"); | ||
const koa_bodyparser_1 = tslib_1.__importDefault(require("koa-bodyparser")); | ||
const tsval_1 = require("tsval"); | ||
const binder_1 = require("./binder"); | ||
const framework_1 = require("./framework"); | ||
const common_1 = require("./common"); | ||
const core_1 = require("./core"); | ||
const router_1 = require("./router"); | ||
const log = debug_1.default("plum:app"); | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ----------------------------------- HELPERS ----------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function extractDecorators(route) { | ||
const classDecorator = route.controller.decorators.filter(x => x.name == "Middleware"); | ||
const methodDecorator = route.action.decorators.filter(x => x.name == "Middleware"); | ||
const extract = (d) => d.map(x => x.value).reduce((a, b) => a.concat(b), []); | ||
return extract(classDecorator) | ||
.concat(extract(methodDecorator)) | ||
.reverse(); | ||
} | ||
exports.extractDecorators = extractDecorators; | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ------------------------------- INVOCATIONS ----------------------------------- */ | ||
@@ -40,19 +54,19 @@ /* ------------------------------------------------------------------------------- */ | ||
const validate = (value, i) => config.validator(value, param(i)); | ||
const issues = parameters.map((value, index) => ({ parameter: param(index).name, issues: validate(value, index) })) | ||
.filter(x => Boolean(x.issues)); | ||
log(`[Action Invocation] Validation result ${framework_1.b(util_1.inspect(issues, false, null))}`); | ||
const issues = parameters.map((value, index) => validate(value, index)) | ||
.reduce((a, b) => a.concat(b), []); | ||
log(`[Action Invocation] Validation result ${common_1.b(issues)}`); | ||
if (issues.length > 0) | ||
return new framework_1.ActionResult(issues, 400); | ||
throw new core_1.ValidationError(issues); | ||
} | ||
const result = controller[route.action.name].apply(controller, parameters); | ||
const awaitedResult = await Promise.resolve(result); | ||
const status = config.responseStatus && config.responseStatus[route.method] || 200; | ||
if (result instanceof framework_1.ActionResult) { | ||
result.status = result.status || status; | ||
log(`[Action Invocation] Method: ${framework_1.b(route.method)} Status config: ${framework_1.b(util_1.inspect(config.responseStatus, false, null))} Status: ${framework_1.b(result.status)} `); | ||
return Promise.resolve(result); | ||
if (awaitedResult instanceof core_1.ActionResult) { | ||
awaitedResult.status = awaitedResult.status || status; | ||
log(`[Action Invocation] ActionResult value - Method: ${common_1.b(route.method)} Status config: ${common_1.b(config.responseStatus)} Status: ${common_1.b(result.status)} `); | ||
return awaitedResult; | ||
} | ||
else { | ||
const awaitedResult = await Promise.resolve(result); | ||
log(`[Action Invocation] Method: ${route.method} Status config: ${framework_1.b(util_1.inspect(config.responseStatus, false, null))} Status: ${framework_1.b(status)} `); | ||
return new framework_1.ActionResult(awaitedResult, status); | ||
log(`[Action Invocation] Raw value - Method: ${route.method} Status config: ${common_1.b(config.responseStatus)} Status: ${common_1.b(status)} `); | ||
return new core_1.ActionResult(awaitedResult, status); | ||
} | ||
@@ -62,5 +76,2 @@ } | ||
exports.ActionInvocation = ActionInvocation; | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ------------------------- MIDDLEWARE PIPELINE --------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function pipe(middleware, context, invocation) { | ||
@@ -71,27 +82,77 @@ return middleware.reverse().reduce((prev, cur) => new MiddlewareInvocation(cur, context, prev), invocation); | ||
/* ------------------------------------------------------------------------------- */ | ||
/* --------------------------- REQUEST HANDLER ----------------------------------- */ | ||
/* -------------------------------- FACITLITIES ---------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
class ValidationMiddleware { | ||
async execute(invocation) { | ||
try { | ||
return await invocation.proceed(); | ||
} | ||
catch (e) { | ||
if (e instanceof core_1.ValidationError) { | ||
return new core_1.ActionResult(e.issues, 400); | ||
} | ||
else | ||
throw e; | ||
} | ||
} | ||
} | ||
/** | ||
* Preset configuration for building web api. This facility contains: | ||
* | ||
* body parser: koa-bodyparser | ||
* | ||
* cors: @koa/cors | ||
*/ | ||
class WebApiFacility { | ||
constructor(opt) { | ||
this.opt = opt; | ||
} | ||
async setup(app) { | ||
app.koa.use(koa_bodyparser_1.default(this.opt && this.opt.bodyParser)); | ||
app.koa.use(cors_1.default(this.opt && this.opt.cors)); | ||
app.set({ | ||
validator: (value, meta) => { | ||
const decorators = meta.decorators.filter((x) => x.type === "ValidatorDecorator"); | ||
log(`[Validator] Validating ${common_1.b(value)} metadata: ${common_1.b(meta)}`); | ||
return tsval_1.validate(value, decorators, [meta.name]) | ||
.map(x => ({ messages: x.messages, path: x.path })); | ||
} | ||
}); | ||
app.use(new ValidationMiddleware()); | ||
} | ||
} | ||
exports.WebApiFacility = WebApiFacility; | ||
/** | ||
* Preset configuration for building restful style api. This facility contains: | ||
* | ||
* body parser: koa-bodyparser | ||
* | ||
* cors: @koa/cors | ||
* | ||
* default response status: { get: 200, post: 201, put: 204, delete: 204 } | ||
*/ | ||
class RestfulApiFacility extends WebApiFacility { | ||
async setup(app) { | ||
super.setup(app); | ||
app.set({ responseStatus: { post: 201, put: 204, delete: 204 } }); | ||
} | ||
} | ||
exports.RestfulApiFacility = RestfulApiFacility; | ||
/* ------------------------------------------------------------------------------- */ | ||
/* --------------------------- MAIN APPLICATION ---------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
async function requestHandler(ctx) { | ||
const controllerMiddleware = router_1.extractDecorators(ctx.route); | ||
const controllerMiddleware = extractDecorators(ctx.route); | ||
const pipeline = pipe(controllerMiddleware, ctx, new ActionInvocation(ctx)); | ||
const result = await pipeline.proceed(); | ||
result.execute(ctx); | ||
log(`[Request Handler] ${framework_1.b(ctx.path)} -> ${framework_1.b(ctx.route.controller.name)}.${framework_1.b(ctx.route.action.name)}`); | ||
log(`[Request Handler] Request Query: ${framework_1.b(util_1.inspect(ctx.query, false, null))}`); | ||
log(`[Request Handler] Request Header: ${framework_1.b(util_1.inspect(ctx.headers, false, null))}`); | ||
log(`[Request Handler] Request Body: ${framework_1.b(util_1.inspect(result.body, false, null))}`); | ||
log(`[Request Handler] ${common_1.b(ctx.path)} -> ${common_1.b(ctx.route.controller.name)}.${common_1.b(ctx.route.action.name)}`); | ||
log(`[Request Handler] Request Query: ${common_1.b(ctx.query)}`); | ||
log(`[Request Handler] Request Header: ${common_1.b(ctx.headers)}`); | ||
log(`[Request Handler] Request Body: ${common_1.b(result.body)}`); | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
/* --------------------------- MAIN APPLICATION ---------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
class Plumier { | ||
constructor() { | ||
this.koa = new koa_1.default(); | ||
this.config = { | ||
mode: "debug", | ||
middleware: [], | ||
facilities: [], | ||
controller: path_1.join(process.cwd(), "./controller"), | ||
dependencyResolver: new framework_1.DefaultDependencyResolver() | ||
}; | ||
this.config = Object.assign({}, core_1.DefaultConfiguration, { middleware: [], facilities: [] }); | ||
} | ||
@@ -103,3 +164,3 @@ use(option) { | ||
else { | ||
this.koa.use(framework_1.MiddlewareUtil.toKoa(option)); | ||
this.koa.use(core_1.MiddlewareUtil.toKoa(option)); | ||
} | ||
@@ -109,3 +170,3 @@ return this; | ||
set(config) { | ||
if (framework_1.hasKeyOf(config, "setup")) | ||
if (common_1.hasKeyOf(config, "setup")) | ||
this.config.facilities.push(config); | ||
@@ -121,12 +182,26 @@ else | ||
if (!fs_1.existsSync(this.config.controller)) | ||
throw new Error(framework_1.StringUtil.format(framework_1.errorMessage.ControllerPathNotFound, this.config.controller)); | ||
routes = await router_1.transformModule(this.config.controller); | ||
throw new Error(core_1.errorMessage.ControllerPathNotFound.format(this.config.controller)); | ||
routes = await router_1.transformModule(this.config.controller, [this.config.fileExtension]); | ||
} | ||
else { | ||
else if (Array.isArray(this.config.controller)) { | ||
routes = this.config.controller.map(x => router_1.transformController(x)) | ||
.reduce((a, b) => a.concat(b), []); | ||
} | ||
else { | ||
routes = router_1.transformController(this.config.controller); | ||
} | ||
if (this.config.mode === "debug") | ||
router_1.printAnalysis(router_1.analyzeRoutes(routes)); | ||
await Promise.all(this.config.facilities.map(x => x.setup(this))); | ||
this.koa.use(async (ctx, next) => { | ||
try { | ||
await next(); | ||
} | ||
catch (e) { | ||
if (e instanceof core_1.HttpStatusError) | ||
ctx.throw(e.status, e); | ||
else | ||
ctx.throw(500, e); | ||
} | ||
}); | ||
this.koa.use(router_1.router(routes, this.config, requestHandler)); | ||
@@ -133,0 +208,0 @@ return this.koa; |
/// <reference types="koa-bodyparser" /> | ||
import { Request } from "koa"; | ||
import { Class, TypeConverter, ValueConverter } from "./framework"; | ||
import { FunctionReflection } from "tinspector"; | ||
export declare function booleanConverter(value: any): boolean; | ||
export declare function dateConverter(value: any): Date; | ||
export declare function defaultModelConverter(value: any, type: Class, converters: Map<Function, ValueConverter>): any; | ||
export declare function defaultArrayConverter(value: any[], type: Class, converters: Map<Function, ValueConverter>): any; | ||
export declare function flattenConverters(converters: [Function, ValueConverter][]): Map<Function, ValueConverter>; | ||
export declare const DefaultConverterList: [Function, ValueConverter][]; | ||
export declare function convert(value: any, type: Class | undefined, converters: Map<Function, ValueConverter>): any; | ||
export declare function bindParameter(request: Request, action: FunctionReflection, converter?: TypeConverter): any[]; | ||
import { Class, ParameterProperties, TypeConverter, ValueConverter } from "./core"; | ||
export declare function flattenConverters(converters: TypeConverter[]): Map<Function, ValueConverter>; | ||
export declare function booleanConverter(rawValue: any, prop: ParameterProperties & { | ||
parameterType: Class; | ||
}): boolean; | ||
export declare function numberConverter(rawValue: any, prop: ParameterProperties & { | ||
parameterType: Class; | ||
}): number; | ||
export declare function dateConverter(rawValue: any, prop: ParameterProperties & { | ||
parameterType: Class; | ||
}): Date; | ||
export declare function defaultModelConverter(value: any, prop: ParameterProperties & { | ||
parameterType: Class; | ||
}): any; | ||
export declare function defaultArrayConverter(value: any[], prop: ParameterProperties & { | ||
parameterType: Class; | ||
}): any; | ||
export declare const DefaultConverterList: TypeConverter[]; | ||
export declare function convert(value: any, prop: ParameterProperties): any; | ||
export declare function bindParameter(request: Request, action: FunctionReflection, converter?: TypeConverter[]): any[]; |
@@ -5,64 +5,147 @@ "use strict"; | ||
const debug_1 = tslib_1.__importDefault(require("debug")); | ||
const util_1 = require("util"); | ||
const framework_1 = require("./framework"); | ||
const tinspector_1 = require("tinspector"); | ||
const common_1 = require("./common"); | ||
const core_1 = require("./core"); | ||
const log = debug_1.default("plum:binder"); | ||
function getArrayDecorator(decorators) { | ||
return decorators.find((x) => x.type == "ParameterBinding" && x.name == "Array"); | ||
} | ||
function createConversionError(value, prop) { | ||
const decorator = getArrayDecorator(prop.decorators); | ||
const type = decorator ? `Array<${decorator.typeAnnotation.name}>` : prop.parameterType.name; | ||
log(`[Converter] Unable to convert ${common_1.b(value)} into ${common_1.b(type)}`); | ||
return new core_1.ConversionError({ path: prop.path, type, value }, core_1.errorMessage.UnableToConvertValue.format(value, type, prop.path.join("->"))); | ||
} | ||
function flattenConverters(converters) { | ||
return converters.reduce((a, b) => { a.set(b.type, b.converter); return a; }, new Map()); | ||
} | ||
exports.flattenConverters = flattenConverters; | ||
//some object can't simply convertible to string https://github.com/emberjs/ember.js/issues/14922#issuecomment-278986178 | ||
function safeToString(value) { | ||
try { | ||
return value.toString(); | ||
} | ||
catch (e) { | ||
return "[object Object]"; | ||
} | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
/* -------------------------------- HELPER --------------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ------------------------------- CONVERTER ------------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function booleanConverter(value) { | ||
return ["on", "true", "1", "yes"].some(x => value.toLocaleLowerCase() == x); | ||
function booleanConverter(rawValue, prop) { | ||
const value = safeToString(rawValue); | ||
const list = { | ||
on: true, true: true, "1": true, yes: true, | ||
off: false, false: false, "0": false, no: false | ||
}; | ||
const result = list[value.toLowerCase()]; | ||
log(`[Boolean Converter] Raw: ${common_1.b(value)} Value: ${common_1.b(result)}`); | ||
if (result === undefined) | ||
throw createConversionError(value, prop); | ||
return result; | ||
} | ||
exports.booleanConverter = booleanConverter; | ||
function dateConverter(value) { | ||
return new Date(value); | ||
function numberConverter(rawValue, prop) { | ||
const value = safeToString(rawValue); | ||
const result = Number(value); | ||
if (isNaN(result) || value === "") | ||
throw createConversionError(value, prop); | ||
log(`[Number Converter] Raw: ${common_1.b(value)} Value: ${common_1.b(result)}`); | ||
return result; | ||
} | ||
exports.numberConverter = numberConverter; | ||
function dateConverter(rawValue, prop) { | ||
const value = safeToString(rawValue); | ||
const result = new Date(value); | ||
if (isNaN(result.getTime()) || value === "") | ||
throw createConversionError(value, prop); | ||
log(`[Date Converter] Raw: ${common_1.b(value)} Value: ${common_1.b(result)}`); | ||
return result; | ||
} | ||
exports.dateConverter = dateConverter; | ||
function defaultModelConverter(value, type, converters) { | ||
const getType = (par) => { | ||
const decorator = par.decorators.find((x) => x.type == "ParameterBinding" && x.name == "Array"); | ||
log(`[Model Converter] Constructor parameter ${par.name} decorator ${util_1.inspect(par.decorators, false, null)}`); | ||
if (decorator) | ||
return decorator.typeAnnotation; | ||
function defaultModelConverter(value, prop) { | ||
//--- helper functions | ||
const isConvertibleToObject = (value) => { | ||
if (typeof value == "boolean" || | ||
typeof value == "number" || | ||
typeof value == "string") | ||
return false; | ||
else | ||
return par.typeAnnotation; | ||
return true; | ||
}; | ||
log(`[Model Converter] converting ${framework_1.b(util_1.inspect(value, false, null))} to ${framework_1.b(type.name)} `); | ||
const reflection = tinspector_1.reflect(type); | ||
log(`[Model Converter] model info ${framework_1.b(util_1.inspect(reflection.ctorParameters))} `); | ||
//--- | ||
//if the value already instance of the type then return immediately | ||
//this is possible when using decorator binding such as @bind.request("req") | ||
if (value instanceof prop.parameterType) | ||
return value; | ||
//get reflection metadata of the class | ||
const reflection = tinspector_1.reflect(prop.parameterType); | ||
//check if the value is possible to convert to model | ||
if (!isConvertibleToObject(value)) | ||
throw createConversionError(value, prop); | ||
log(`[Model Converter] converting ${common_1.b(value)} to ${common_1.b(prop.parameterType.name)}{${common_1.b(reflection.ctorParameters.map(x => x.name).join(", "))}}`); | ||
//sanitize excess property to prevent object having properties that doesn't match with declaration | ||
//traverse through the object properties and convert to appropriate property's type | ||
const sanitized = reflection.ctorParameters.map(x => ({ | ||
name: x.name, | ||
value: convert(value[x.name], getType(x), converters) | ||
value: convert(value[x.name], { | ||
parameterType: x.typeAnnotation, | ||
path: prop.path.concat(x.name), | ||
decorators: x.decorators, | ||
converters: prop.converters | ||
}) | ||
})).reduce((a, b) => { a[b.name] = b.value; return a; }, {}); | ||
log(`[Model Converter] Sanitized value ${framework_1.b(util_1.inspect(sanitized, false, null))}`); | ||
return Object.assign(new type(), sanitized); | ||
log(`[Model Converter] Sanitized value ${common_1.b(sanitized)}`); | ||
//crete new instance of the type and assigned the sanitized values | ||
try { | ||
return Object.assign(new prop.parameterType(), sanitized); | ||
} | ||
catch (e) { | ||
const message = core_1.errorMessage.UnableToInstantiateModel.format(prop.parameterType.name); | ||
if (e instanceof Error) { | ||
e.message = message + "\n" + e.message; | ||
throw e; | ||
} | ||
else | ||
throw new Error(message); | ||
} | ||
} | ||
exports.defaultModelConverter = defaultModelConverter; | ||
function defaultArrayConverter(value, type, converters) { | ||
log(`[Array Converter] converting ${framework_1.b(util_1.inspect(value, false, null))} to Array<${type.name}>`); | ||
return value.map(x => convert(x, type, converters)); | ||
function defaultArrayConverter(value, prop) { | ||
const decorator = getArrayDecorator(prop.decorators); | ||
if (!Array.isArray(value)) | ||
throw createConversionError(value, prop); | ||
log(`[Array Converter] converting ${common_1.b(value)} to Array<${decorator.typeAnnotation.name}>`); | ||
return value.map(((x, i) => convert(x, { | ||
path: prop.path.concat(i.toString()), | ||
converters: prop.converters, | ||
decorators: [], | ||
parameterType: decorator.typeAnnotation | ||
}))); | ||
} | ||
exports.defaultArrayConverter = defaultArrayConverter; | ||
function flattenConverters(converters) { | ||
return converters.reduce((a, b) => { a.set(b[0], b[1]); return a; }, new Map()); | ||
} | ||
exports.flattenConverters = flattenConverters; | ||
exports.DefaultConverterList = [ | ||
[Number, Number], | ||
[Date, dateConverter], | ||
[Boolean, booleanConverter] | ||
{ type: Number, converter: numberConverter }, | ||
{ type: Date, converter: dateConverter }, | ||
{ type: Boolean, converter: booleanConverter } | ||
]; | ||
function convert(value, type, converters) { | ||
if (!type) | ||
function convert(value, prop) { | ||
if (value === null || value === undefined) | ||
return undefined; | ||
if (!prop.parameterType) | ||
return value; | ||
if (Array.isArray(value)) | ||
return defaultArrayConverter(value, type, converters); | ||
else if (converters.has(type)) | ||
return converters.get(type)(value, type, converters); | ||
log(`[Converter] Path: ${common_1.b(prop.path.join("->"))} ` + | ||
`Source Type: ${common_1.b(typeof value)} ` + | ||
`Target Type:${common_1.b(prop.parameterType.name)} ` + | ||
`Decorators: ${common_1.b(prop.decorators.map(x => x.name).join(", "))} ` + | ||
`Value: ${common_1.b(value)}`); | ||
//check if the parameter contains @bind.array() | ||
if (Boolean(getArrayDecorator(prop.decorators))) | ||
return defaultArrayConverter(value, Object.assign({}, prop, { parameterType: prop.parameterType })); | ||
//check if parameter is native value that has default converter (Number, Date, Boolean) or if user provided converter | ||
else if (prop.converters.has(prop.parameterType)) | ||
return prop.converters.get(prop.parameterType)(value, Object.assign({}, prop, { parameterType: prop.parameterType })); | ||
//if type of model and has no converter, use DefaultObject converter | ||
else if (framework_1.isCustomClass(type)) | ||
return defaultModelConverter(value, type, converters); | ||
else if (common_1.isCustomClass(prop.parameterType)) | ||
return defaultModelConverter(value, Object.assign({}, prop, { parameterType: prop.parameterType })); | ||
//no converter, return the value | ||
@@ -74,3 +157,3 @@ else | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ----------------------------- MODEL BINDER ------------------------------------ */ | ||
/* ----------------------------- BINDER FUNCTIONS -------------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
@@ -80,10 +163,8 @@ function bindModel(action, request, par, converter) { | ||
return; | ||
if (!framework_1.isCustomClass(par.typeAnnotation)) | ||
if (!common_1.isCustomClass(par.typeAnnotation)) | ||
return; | ||
log(`[Model Binder] Action: ${framework_1.b(action.name)} Parameter: ${framework_1.b(par.name)} Parameter Type: ${framework_1.b(par.typeAnnotation.name)}`); | ||
log(`[Model Binder] Action: ${common_1.b(action.name)} Parameter: ${common_1.b(par.name)} Parameter Type: ${common_1.b(par.typeAnnotation.name)}`); | ||
log(`[Model Binder] Request Body: ${common_1.b(request.body)}`); | ||
return converter(request.body); | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
/* ------------------------ DECORATOR PARAMETER BINDER --------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function bindDecorator(action, request, par, converter) { | ||
@@ -93,38 +174,33 @@ const decorator = par.decorators.find((x) => x.type == "ParameterBinding"); | ||
return; | ||
log(`[Decorator Binder] Action: ${framework_1.b(action.name)} Parameter: ${framework_1.b(par.name)} Decorator: ${framework_1.b(decorator.name)} Part: ${framework_1.b(decorator.part)}`); | ||
log(`[Decorator Binder] Action: ${common_1.b(action.name)} Parameter: ${common_1.b(par.name)} Decorator: ${common_1.b(decorator.name)} Part: ${common_1.b(decorator.part)}`); | ||
const getDataOrPart = (data) => decorator.part ? data && data[decorator.part] : data; | ||
switch (decorator.name) { | ||
case "Body": | ||
return decorator.part ? request.body && request.body[decorator.part] : request.body; | ||
return converter(getDataOrPart(request.body)); | ||
case "Query": | ||
return decorator.part ? request.query && request.query[decorator.part] : request.query; | ||
return converter(getDataOrPart(request.query)); | ||
case "Header": | ||
return decorator.part ? request.headers && request.headers[decorator.part] : request.headers; | ||
return converter(getDataOrPart(request.headers)); | ||
case "Request": | ||
return decorator.part ? request[decorator.part] : request; | ||
return converter(getDataOrPart(request)); | ||
} | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
/* --------------------------- ARRAY PARAMETER BINDER ---------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function bindArrayDecorator(action, request, par, converter) { | ||
const decorator = par.decorators.find((x) => x.type == "ParameterBinding" && x.name == "Array"); | ||
const decorator = getArrayDecorator(par.decorators); | ||
if (!decorator) | ||
return; | ||
log(`[Array Binder] Action: ${framework_1.b(action.name)} Parameter: ${framework_1.b(par.name)} Type: ${framework_1.b(decorator.typeAnnotation.name)}`); | ||
log(`[Array Binder] Action: ${common_1.b(action.name)} Parameter: ${common_1.b(par.name)} Type: ${common_1.b(decorator.typeAnnotation.name)}`); | ||
return converter(request.body, decorator.typeAnnotation); | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
/* -------------------------- REGULAR PARAMETER BINDER --------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function bindRegular(action, request, par, converter) { | ||
log(`[Regular Binder] Action: ${framework_1.b(action.name)} Parameter: ${framework_1.b(par.name)} Value: ${framework_1.b(request.query[par.name])}`); | ||
log(`[Regular Binder] Action: ${common_1.b(action.name)} Parameter: ${common_1.b(par.name)} Value: ${common_1.b(request.query[par.name])}`); | ||
return converter(request.query[par.name.toLowerCase()]); | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
/* -------------------------- MAIN PARAMETER BINDER --------------------------- */ | ||
/* ------------------------------------------------------------------------------- */ | ||
function bindParameter(request, action, converter) { | ||
const mergedConverters = flattenConverters(exports.DefaultConverterList.concat(converter || [])); | ||
return action.parameters.map(x => { | ||
const converter = (result, type) => convert(result, type || x.typeAnnotation, mergedConverters); | ||
return action.parameters.map(((x, i) => { | ||
const converter = (result, type) => convert(result, { | ||
path: [x.name], parameterType: type || x.typeAnnotation, | ||
converters: mergedConverters, decorators: x.decorators | ||
}); | ||
return bindArrayDecorator(action, request, x, converter) || | ||
@@ -134,4 +210,4 @@ bindDecorator(action, request, x, converter) || | ||
bindRegular(action, request, x, converter); | ||
}); | ||
})); | ||
} | ||
exports.bindParameter = bindParameter; |
@@ -1,2 +0,3 @@ | ||
export { ActionResult, Application, bind, Configuration, DependencyResolver, Facility, HeaderPart, HttpMethod, HttpStatusError, Invocation, KoaMiddleware, middleware, Middleware, model, PlumierApplication, PlumierConfiguration, RequestPart, route, RouteInfo, TypeConverter, WebApiFacility } from "./framework"; | ||
export { Plumier } from "./application"; | ||
export { ActionResult, Application, bind, Configuration, DependencyResolver, Facility, HeaderPart, HttpMethod, HttpStatusError, Invocation, KoaMiddleware, middleware, Middleware, model, PlumierApplication, PlumierConfiguration, RequestPart, route, RouteInfo, TypeConverter, } from "./core"; | ||
export { Plumier, WebApiFacility, RestfulApiFacility } from "./application"; | ||
export { val } from "tsval"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var framework_1 = require("./framework"); | ||
exports.ActionResult = framework_1.ActionResult; | ||
exports.bind = framework_1.bind; | ||
exports.HttpStatusError = framework_1.HttpStatusError; | ||
exports.middleware = framework_1.middleware; | ||
exports.model = framework_1.model; | ||
exports.route = framework_1.route; | ||
exports.WebApiFacility = framework_1.WebApiFacility; | ||
var core_1 = require("./core"); | ||
exports.ActionResult = core_1.ActionResult; | ||
exports.bind = core_1.bind; | ||
exports.HttpStatusError = core_1.HttpStatusError; | ||
exports.middleware = core_1.middleware; | ||
exports.model = core_1.model; | ||
exports.route = core_1.route; | ||
var application_1 = require("./application"); | ||
exports.Plumier = application_1.Plumier; | ||
exports.WebApiFacility = application_1.WebApiFacility; | ||
exports.RestfulApiFacility = application_1.RestfulApiFacility; | ||
var tsval_1 = require("tsval"); | ||
exports.val = tsval_1.val; |
import { Context } from "koa"; | ||
import { RouteInfo, Class, Configuration, Middleware } from "./framework"; | ||
import { ClassReflection } from "tinspector"; | ||
import { Class, Configuration, RouteInfo } from "./core"; | ||
interface Issue { | ||
@@ -14,5 +14,4 @@ type: "error" | "warning" | "success"; | ||
export declare function getControllerRoute(controller: ClassReflection): string; | ||
export declare function extractDecorators(route: RouteInfo): Middleware[]; | ||
export declare function transformController(object: ClassReflection | Class): RouteInfo[]; | ||
export declare function transformModule(path: string, extensions?: string[]): Promise<RouteInfo[]>; | ||
export declare function transformModule(path: string, extensions: string[]): Promise<RouteInfo[]>; | ||
export declare function router(infos: RouteInfo[], config: Configuration, handler: (ctx: Context) => Promise<void>): (ctx: Context, next: () => Promise<void>) => Promise<void>; | ||
@@ -19,0 +18,0 @@ export declare function analyzeRoutes(routes: RouteInfo[]): TestResult[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const tslib_1 = require("tslib"); | ||
const chalk_1 = tslib_1.__importDefault(require("chalk")); | ||
const debug_1 = tslib_1.__importDefault(require("debug")); | ||
@@ -8,6 +9,5 @@ const Fs = tslib_1.__importStar(require("fs")); | ||
const path_to_regexp_1 = tslib_1.__importDefault(require("path-to-regexp")); | ||
const framework_1 = require("./framework"); | ||
const tinspector_1 = require("tinspector"); | ||
const util_1 = require("util"); | ||
const chalk_1 = tslib_1.__importDefault(require("chalk")); | ||
const common_1 = require("./common"); | ||
const core_1 = require("./core"); | ||
const log = debug_1.default("plum:router"); | ||
@@ -26,16 +26,6 @@ /* ------------------------------------------------------------------------------- */ | ||
exports.getControllerRoute = getControllerRoute; | ||
function extractDecorators(route) { | ||
const classDecorator = route.controller.decorators.filter(x => x.name == "Middleware"); | ||
const methodDecorator = route.action.decorators.filter(x => x.name == "Middleware"); | ||
const extract = (d) => d.map(x => x.value).reduce((a, b) => a.concat(b), []); | ||
return extract(classDecorator) | ||
.concat(extract(methodDecorator)) | ||
.reverse(); | ||
} | ||
exports.extractDecorators = extractDecorators; | ||
function getActionName(route) { | ||
return `${route.controller.name}.${route.action.name}(${route.action.parameters.map(x => x.name).join(", ")})`; | ||
} | ||
function resolvePath(path, extensions) { | ||
const ext = extensions || [".js"]; | ||
function resolvePath(path, ext) { | ||
//resolve provided path directory or file | ||
@@ -121,2 +111,3 @@ if (Fs.lstatSync(path).isDirectory()) { | ||
const match = regexp.exec(ctx.path); | ||
log(`[Router] Route: ${common_1.b(route.method)} ${common_1.b(route.url)} Ctx Path: ${common_1.b(ctx.method)} ${common_1.b(ctx.path)} Match: ${common_1.b(match)}`); | ||
return { keys, match, method: route.method.toUpperCase(), route }; | ||
@@ -129,3 +120,3 @@ } | ||
if (match) { | ||
log(`[Router] Match route ${framework_1.b(match.route.method)} ${framework_1.b(match.route.url)} with ${framework_1.b(ctx.method)} ${framework_1.b(ctx.path)}`); | ||
log(`[Router] Match route ${common_1.b(match.route.method)} ${common_1.b(match.route.url)} with ${common_1.b(ctx.method)} ${common_1.b(ctx.path)}`); | ||
//assign config and route to context | ||
@@ -138,3 +129,3 @@ Object.assign(ctx, { config, route: match.route }); | ||
}, {}); | ||
log(`[Router] Extracted parameter from url ${framework_1.b(util_1.inspect(query, false, null))}`); | ||
log(`[Router] Extracted parameter from url ${common_1.b(query)}`); | ||
Object.assign(ctx.query, query); | ||
@@ -144,3 +135,3 @@ await handler(ctx); | ||
else { | ||
log(`[Router] Not route match ${framework_1.b(ctx.method)} ${framework_1.b(ctx.url)}`); | ||
log(`[Router] Not route match ${common_1.b(ctx.method)} ${common_1.b(ctx.url)}`); | ||
await next(); | ||
@@ -156,3 +147,3 @@ } | ||
function getModelsInParameters(par) { | ||
return par.filter(x => x.typeAnnotation && framework_1.isCustomClass(x.typeAnnotation)) | ||
return par.filter(x => x.typeAnnotation && common_1.isCustomClass(x.typeAnnotation)) | ||
.map(x => tinspector_1.reflect(x.typeAnnotation)); | ||
@@ -185,3 +176,3 @@ } | ||
type: "error", | ||
message: framework_1.StringUtil.format(framework_1.errorMessage.RouteDoesNotHaveBackingParam, missing.join(", ")) | ||
message: core_1.errorMessage.RouteDoesNotHaveBackingParam.format(missing.join(", ")) | ||
}; | ||
@@ -198,3 +189,3 @@ } | ||
type: "warning", | ||
message: framework_1.errorMessage.ActionDoesNotHaveTypeInfo | ||
message: core_1.errorMessage.ActionDoesNotHaveTypeInfo | ||
}; | ||
@@ -210,3 +201,3 @@ } | ||
type: "error", | ||
message: framework_1.errorMessage.MultipleDecoratorNotSupported | ||
message: core_1.errorMessage.MultipleDecoratorNotSupported | ||
}; | ||
@@ -222,3 +213,3 @@ } | ||
type: "error", | ||
message: framework_1.StringUtil.format(framework_1.errorMessage.DuplicateRouteFound, dup.map(x => getActionName(x)).join(" ")) | ||
message: core_1.errorMessage.DuplicateRouteFound.format(dup.map(x => getActionName(x)).join(" ")) | ||
}; | ||
@@ -236,6 +227,6 @@ } | ||
if (noTypeInfo.length > 0) { | ||
log(`[Analyzer] Model without type information ${framework_1.b(noTypeInfo.map(x => x.name).join(", "))}`); | ||
log(`[Analyzer] Model without type information ${common_1.b(noTypeInfo.map(x => x.name).join(", "))}`); | ||
return { | ||
type: "warning", | ||
message: framework_1.StringUtil.format(framework_1.errorMessage.ModelWithoutTypeInformation, noTypeInfo.map(x => x.name).join(", ")) | ||
message: core_1.errorMessage.ModelWithoutTypeInformation.format(noTypeInfo.map(x => x.name).join(", ")) | ||
}; | ||
@@ -253,3 +244,3 @@ } | ||
type: "warning", | ||
message: framework_1.StringUtil.format(framework_1.errorMessage.ArrayWithoutTypeInformation, array.map(x => x.name).join(", ")) | ||
message: core_1.errorMessage.ArrayWithoutTypeInformation.format(array.map(x => x.name).join(", ")) | ||
}; | ||
@@ -278,3 +269,3 @@ } | ||
const data = results.map(x => { | ||
const method = framework_1.StringUtil.padRight(x.route.method.toUpperCase(), 5); | ||
const method = x.route.method.toUpperCase(); | ||
const action = getActionName(x.route); | ||
@@ -285,9 +276,9 @@ const issues = x.issues.map(issue => ` - ${issue.type} ${issue.message}`); | ||
data.forEach((x, i) => { | ||
const action = framework_1.StringUtil.padRight(x.action, Math.max(...data.map(x => x.action.length))); | ||
const method = framework_1.StringUtil.padRight(x.method, Math.max(...data.map(x => x.method.length))); | ||
const url = framework_1.StringUtil.padRight(x.url, Math.max(...data.map(x => x.url.length))); | ||
const action = x.action.padEnd(Math.max(...data.map(x => x.action.length))); | ||
const method = x.method.padEnd(Math.max(...data.map(x => x.method.length))); | ||
//const url = x.url.padEnd(Math.max(...data.map(x => x.url.length))) | ||
const issueColor = (issue) => issue.startsWith(" - warning") ? chalk_1.default.yellow(issue) : chalk_1.default.red(issue); | ||
const color = x.issues.length == 0 ? (x) => x : | ||
x.issues.some(x => x.startsWith(" - warning")) ? chalk_1.default.yellow : chalk_1.default.red; | ||
console.log(color(`${i + 1}. ${action} -> ${method} ${url}`)); | ||
console.log(color(`${i + 1}. ${action} -> ${method} ${x.url}`)); | ||
x.issues.forEach(issue => console.log(issueColor(issue))); | ||
@@ -294,0 +285,0 @@ }); |
{ | ||
"name": "plumier", | ||
"version": "0.1.0-alpha.b123904d", | ||
"version": "0.1.0-alpha.caa0578b", | ||
"description": "Pleasant TypeScript Web Api Framework", | ||
@@ -17,4 +17,4 @@ "main": "lib/index.js", | ||
"compile": "tsc -p tsconfig.build.json", | ||
"package-prod": "node ../../.script/modify-package.js production", | ||
"package-dev": "node ../../.script/modify-package.js" | ||
"package-prod": "node ../../.script/modify-package production", | ||
"package-dev": "node ../../.script/modify-package" | ||
}, | ||
@@ -36,5 +36,5 @@ "author": "Ketut Sandiarsa", | ||
"path-to-regexp": "^2.2.1", | ||
"tinspector": "1.4.0-alpha.b123904d", | ||
"tinspector": "1.4.0-alpha.caa0578b", | ||
"tslib": "^1.9.2", | ||
"validator": "^10.4.0" | ||
"validatorts": "^0.1.0" | ||
}, | ||
@@ -44,3 +44,2 @@ "devDependencies": { | ||
"benalu": "^2.0.0-beta-1", | ||
"edit-json-file": "^1.0.8", | ||
"supertest": "^3.1.0" | ||
@@ -47,0 +46,0 @@ }, |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
64327
3
1673
1
+ Addedvalidatorts@^0.1.0
+ Added@types/node@22.13.5(transitive)
+ Addedget-intrinsic@1.3.0(transitive)
+ Addedtinspector@1.4.0-alpha.caa0578b(transitive)
- Removedvalidator@^10.4.0
- Removed@types/node@22.13.4(transitive)
- Removedget-intrinsic@1.2.7(transitive)
- Removedtinspector@1.4.0-alpha.b123904d(transitive)
- Removedvalidator@10.11.0(transitive)