koa-ts-core 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
+
type TagSpecOptions = {
|
|
3
|
+
/** 取 path 前缀的段数:2 => /api/products */
|
|
4
|
+
level?: number;
|
|
5
|
+
/** 只有在 operation 没有 tags 时才写入;false=强制覆盖 */
|
|
6
|
+
onlyIfEmpty?: boolean;
|
|
7
|
+
};
|
|
1
8
|
export declare class SwaggerCollector {
|
|
2
9
|
private static instance;
|
|
3
10
|
private paths;
|
|
@@ -11,6 +18,13 @@ export declare class SwaggerCollector {
|
|
|
11
18
|
*/
|
|
12
19
|
addRoute(controllerClass: any, functionName: string, method: string, fullPath: string): void;
|
|
13
20
|
loadDocMetadata(): void;
|
|
21
|
+
/**
|
|
22
|
+
* 将 paths 节点按前缀分组
|
|
23
|
+
* @param paths OpenAPI paths 节点
|
|
24
|
+
* @param level 分组层级,默认 2
|
|
25
|
+
* @returns 返回分组后的 paths 和 tags
|
|
26
|
+
*/
|
|
27
|
+
tagSpecByPrefix<T extends OpenAPIV3.Document>(spec: T, options?: TagSpecOptions): T;
|
|
14
28
|
generateSpec(): {
|
|
15
29
|
openapi: string;
|
|
16
30
|
info: {
|
|
@@ -24,3 +38,4 @@ export declare class SwaggerCollector {
|
|
|
24
38
|
};
|
|
25
39
|
};
|
|
26
40
|
}
|
|
41
|
+
export {};
|
package/dist/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";require("reflect-metadata");var e=require("async_hooks"),t=require("fs"),s=require("path"),r=require("@koa/router"),o=require("koa"),a=require("portfinder"),n=require("dotenv"),c=require("log4js"),i=require("koa-bodyparser"),d=require("koa2-cors"),p=require("crypto"),u=require("koa2-swagger-ui");const l=["get","post","put","delete","patch","options"],h=0,g="index",m="get_",f=[g,m,...l],y=Symbol("route_metadata");function w(e){const t=e.trim();if(!t)return"";return`/${t.replace(/^\/+/,"")}`}function x(e,t,s=!1){return(r,o,a)=>{if("function"!=typeof a.value)throw new Error(`@Router/@AuthRouter 只能用于方法:${o}`);let n=Reflect.getMetadata(y,r.constructor);n||(n=new Map,Reflect.defineMetadata(y,n,r.constructor));const c=function(e,t){return e||(t===m?"get":l.includes(t)?t:"get")}(e,o),i=function(e,t){return null!=e?w(e):f.includes(t)?"":w(t)}(t,o),d={handler:a.value,path:i,method:c,functionName:o,authRequired:s,middlewares:[],className:r.constructor.name,apiPrePath:""};return n.set(o,d),a}}const v=new e.AsyncLocalStorage,P=()=>{const e=v.getStore();if(!e)throw new Error("context is not exist");return e},k=e=>{const{success:t=!0,errorCode:s=-1,message:r="success",errorMessage:o="error request",data:a=null,statusCode:n=200}=e??{},c=P(),i={code:t?h:s,message:t?r:o,...null!=a?{data:a}:{}};c.trackId&&(i.trackId=c.trackId),c.body=i,c.status=n},b=(e,t)=>{k({success:!1,errorMessage:t?.message,errorCode:t?.code,data:t?.data,statusCode:e})};class q extends Error{constructor(e){super(e?.message||"An unexpected error occurred"),this.name=this.constructor.name,this.code=e?.code??-1,this.statusCode=500,"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.stack=this.stack??new Error(this.message).stack}get toRspOptions(){return{code:this.code,message:this.message,data:this.stack}}}const M=()=>{const e=process.cwd();if(t.existsSync(s.resolve(e,"controller")))return e;const r=s.resolve(e,"src");if(t.existsSync(r))return r;const o=s.resolve(e,"dist");if(t.existsSync(o))return o;const a=s.resolve(e,"build");return t.existsSync(a)?a:r},E=()=>{const e=M();return{controllerModule:s.resolve(e,"controller"),validateModule:s.resolve(e,"validate"),viewModule:s.resolve(e,"view"),docModule:s.resolve(e,"doc")}},R=e=>{if(t.existsSync(e))try{const t=require(e);return t.default||t}catch(t){throw console.error(`[koa-ts-core] Failed to load module: ${e}`,t),t}};function S(e,r){t.existsSync(e)&&t.readdirSync(e,{withFileTypes:!0}).forEach(t=>{const o=s.resolve(e,t.name);t.isFile()?r({path:e,name:t.name,wholePath:o},t):t.isDirectory()&&S(o,r)})}const N=e=>e.split(s.sep).join("/"),A=e=>{const t=N(e).replace(/^\/+/,"");return t?`/${t}`:""},C=(e,t)=>{const s=N(e.path).split("/"),r=s.lastIndexOf(t);if(-1===r){const s=e.path.indexOf(t);if(-1===s)throw new Error(`'${t}' not found in path: ${e.path}`);const r=e.path.substring(s+t.length),o=A(r),a=e.name?.split(".")?.[0];return{pathPrefix:o,moduleName:a,name:e.name}}const o=s.slice(r+1).join("/"),a=A(o),n=e.name?.split(".")?.[0];return{pathPrefix:a,moduleName:n,name:e.name}};function I(e){try{return t.statSync(e).isDirectory()}catch{return!1}}var O;exports.MiddlewarePhase=void 0,(O=exports.MiddlewarePhase||(exports.MiddlewarePhase={})).AsyncContext="AsyncContext",O.ErrorHandling="ErrorHandling",O.Logging="Logging",O.Security="Security",O.BodyParsing="BodyParsing",O.Auth="Auth",O.Routing="Routing";const $=new r,_=async e=>{if(!process.env.APP_PORT)try{const e=await a.getPortPromise({port:8e3,stopPort:9999});process.env.APP_PORT=String(e)}catch(e){console.warn("[koa-ts-core] Failed to find available port automatically.")}const t=e.listen;e.listen=function(...e){if(0===e.length&&process.env.APP_PORT){const t=Number(process.env.APP_PORT);isNaN(t)||e.push(t)}const s=t.apply(this,e);return s.on("listening",()=>(e=>{const t=e.address();if(t)if("string"==typeof t)console.log(`Server listening on ${t}`);else{const{port:e}=t;console.log(`Server running at http://localhost:${e}`)}})(s)),s}},D=Symbol("validate_metadata"),j=Symbol("swagger_tags"),T=Symbol("swagger_operation"),L=Symbol("swagger_parameters"),B=Symbol("swagger_body"),U=Symbol("swagger_responses"),H={DOCUMENTATION_DIR:s.join(process.cwd(),"doc")},F=()=>{const e=[];return I(H.DOCUMENTATION_DIR)?(S(H.DOCUMENTATION_DIR,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const{pathPrefix:s}=C(t,"doc"),r=((e,t)=>{const s=R(e.wholePath);if(!s)return{configs:[],desc:"",name:"",pathPrefix:t,apiPrePath:""};const r=s.prototype,o=Object.getOwnPropertyNames(r).filter(e=>"function"==typeof r[e]&&"constructor"!==e),a=e.path.indexOf("doc"),n=a>=0?A(e.path.substring(a+3)):"";let c=n;if(e.name&&!e.name.startsWith("index.")){const t=e.name.split(".")[0];c=`${n}/${t}`}const i=o.map(e=>{const t=r[e];if("function"==typeof t){const e=t();return e.path=`${c}${e.path}`,e}return null}).filter(e=>!!e);let d="";if(i[0]?.path){const e=i[0].path.lastIndexOf("/");e>-1&&(d=i[0].path.substring(0,e))}return{configs:i,desc:s.desc,name:s.name,pathPrefix:t,apiPrePath:d}})(t,s);r.configs.length>0&&e.push(r)}),e):[]};class V{constructor(){this.paths={}}static getInstance(){return this.instance||(this.instance=new V),this.instance}addRoute(e,t,s,r){const o=r.replace(/:([a-zA-Z0-9_]+)/g,"{$1}");this.paths[o]||(this.paths[o]={});const a=e.prototype,n=Reflect.getMetadata(T,a,t)||{},c=n.summary||t,i=n.description,d=Reflect.getMetadata(j,e)||[e.name],p=Reflect.getMetadata(L,a,t)||[],u=Reflect.getMetadata(B,a,t);let l;u&&(l={description:u.description,required:u.required,content:{"application/json":{schema:u.schema||{type:"object"}}}});const h=Reflect.getMetadata(U,a,t)||[],g={};h.forEach(e=>{g[e.status]={description:e.description||"",content:e.schema?{"application/json":{schema:e.schema}}:void 0}}),0===Object.keys(g).length&&(g[200]={description:"Success"}),this.paths[o][s.toLowerCase()]={tags:d,summary:c,description:i,parameters:p,requestBody:l,responses:g}}loadDocMetadata(){F().forEach(e=>{const t=e.desc||e.name;e.configs.forEach(e=>{const s=("/"+e.path).replace(/\/+/g,"/"),r=s.replace(/:([a-zA-Z0-9_]+)/g,"{$1}"),o=e.method.toLowerCase();this.paths[r]||(this.paths[r]={}),this.paths[r][o]||(this.paths[r][o]={});const a=this.paths[r][o];a.summary||(a.summary=e.description),a.tags&&0!==a.tags.length||(a.tags=[t]);const n=s.match(/:([a-zA-Z0-9_]+)/g);if(n&&(a.parameters||(a.parameters=[]),n.forEach(e=>{const t=e.substring(1);a.parameters.find(e=>e.name===t&&"path"===e.in)||a.parameters.push({name:t,in:"path",required:!0,schema:{type:"string"},description:t})})),e.request?.query&&(a.parameters||(a.parameters=[]),Object.keys(e.request.query).forEach(t=>{if(!a.parameters.find(e=>e.name===t&&"query"===e.in)){const s=e.request.query[t];s&&"object"==typeof s&&a.parameters.push({name:t,in:"query",description:s.description,required:!1!==s.required,schema:s.schema??{type:s.type??"string"},example:s.example})}})),e.request?.header&&(a.parameters||(a.parameters=[]),Object.keys(e.request.header).forEach(t=>{if("content-type"===t.toLowerCase())return;if(!a.parameters.find(e=>e.name===t&&"header"===e.in)){const s=e.request.header[t];s&&"object"==typeof s&&a.parameters.push({name:t,in:"header",description:s.description,required:!1!==s.required,schema:s.schema??{type:s.type??"string"},example:s.example})}})),e.request?.body&&Object.keys(e.request.body).length>0&&!a.requestBody){const t=e.request.body;a.requestBody={description:t.description,required:t.required,content:{"application/json":{schema:t.schema||{type:"object"},example:t.example}}}}e.response?.body&&(a.responses||(a.responses={}),a.responses[200]||(a.responses[200]={description:"Success",content:{"application/json":{schema:{type:"object",example:e.response.body}}}}))})})}generateSpec(){return{openapi:"3.0.0",info:{title:"API Documentation",version:"1.0.0",description:"Auto generated by koa-ts-core"},paths:this.paths,components:{schemas:{}}}}}const z=(e,t,s,r,o)=>{if(!r||0===r.size)return;const{pathPrefix:a,moduleName:n,name:c}=C(t,"controller"),i=`${a}/${n}`,d=R(`${e}${a}/${c}`),p=e=>async(t,s)=>{const r=e(t);r instanceof Promise&&await r,await s()};r.forEach(e=>{const{method:t,path:r,handler:n,functionName:c,authRequired:u,middlewares:l}=e,h=[...l],g=Reflect.getMetadata(D,s.prototype,c);g?h.push(p(g)):d&&"function"==typeof d[c]&&h.push(p(d[c].bind(d))),h.push(((e,t)=>async(s,r)=>{let a=o(e,s);return a instanceof Promise&&(a=await a),a?t(s,r):s.throw(403,"无权限")})(u,n));const m=W(r,i,c,h,t);V.getInstance().addRoute(s,c,t,m),e.path=m,e.apiPrePath=a})},W=(e,t,s,r,o)=>{const a=`${t}${e}`;if(s===g){const e=`${t}/index`;$[o](e,...r)}return $[o](a,...r),a};let Z=null;const K=()=>(Z||(Z=c.configure({appenders:{console:{type:"console"},file:{type:"dateFile",filename:"logs/app.log",pattern:".yyyy-MM-dd",compress:!0,numBackups:7,alwaysIncludePattern:!0}},categories:{default:{appenders:["console","file"],level:"info"}}})),Z),Q=(e,t)=>v.run(e,()=>t()),X=e=>{const{generator:t,exposeHeader:s=!0,headerName:r="x-request-id"}=e;return!1===t?async(e,t)=>{await t()}:async(e,o)=>{const a=await t(e);a&&(e.trackId=a,s&&e.set(r,a)),await o()}},Y=async(e,t)=>{const s=Date.now(),r=K().getLogger(),o=e.runtimeLog;o&&r.info("[REQUEST] %s %s %s - ip: %s - ua: %s",e.trackId,e.method,e.url,e.ip,e.get("user-agent")||"-");try{e.hook?.(e,"request"),await t(),e.hook?.(e,"response")}catch(t){const a=Date.now()-s;throw o&&r.error("[ERROR] %s %s %s - status: %s - cost: %dms - message: %s - stack: %s",e.trackId,e.method,e.url,t.status||500,a,t.message||"unknown error",t.stack||""),t}const a=Date.now()-s;o&&r.info("[RESPONSE] %s %s %s - status: %d - cost: %dms - length: %s",e.trackId,e.method,e.url,e.status,a,e.length||e.response.length||"-")},G=async(e,t)=>{e.extra={get:e.query,post:e.request.body??{}},await t()};(()=>{const e=process.env.NODE_ENV||"production";let t;const r=process.env.ENV_DIR;if(r){if(t=s.isAbsolute(r)?r:s.resolve(process.cwd(),r),!I(t))throw new Error(`[koa-ts-core] ENV_DIR is set but directory not found: ${t}`)}else{const e=M();let r=s.resolve(e,"env");if(I(r)||(r=s.resolve(e,"..","env")),!I(r))return void console.warn(`[koa-ts-core] env directory not found: tried ${s.resolve(e,"env")} and ${s.resolve(e,"..","env")}`);t=r}console.log("[koa-ts-core] load env path:",t),n.config({path:s.resolve(t,".env.local")});const o={development:".development.env",test:".test.env",production:".production.env"}[e];o&&n.config({path:s.resolve(t,`${o}.local`)}),o&&n.config({path:s.resolve(t,o)}),n.config({path:s.resolve(t,".common.env")})})(),global.isDev="development"===process.env.NODE_ENV;const J=e=>{const{koaInstance:t,auth:s,error:r,log:a,hooks:n,phaseMiddlewares:c,koa2Cors:i,trackId:d,swagger:u,bodyParser:l}=e;return{app:t??new o,auth:{handler:s?.handler},error:{handler:r?.handler,exposeStack:r?.exposeStack??"production"!==process.env.NODE_ENV},log:{log4:a?.log4??!1,runtimeLog:a?.runtimeLog??!0},hooks:{register:n?.register},phaseMiddlewares:c??new Map,koa2Cors:i,trackId:{generator:!1!==d?.generator&&(async e=>{const t=d?.headerName??"x-request-id",s=e.get(t);return s||p.randomUUID()}),exposeHeader:d?.exposeHeader??!0,headerName:d?.headerName??"x-request-id"},swagger:u,bodyParser:l}},ee=(e,t)=>{const{log:s,hooks:r}=t;e.context.log4=(e=>{if(!1!==e)return"boolean"==typeof e&&!0===e?K():e(c)})(s.log4),e.context.runtimeLog=s.runtimeLog,e.context.hook=r.register,e.context.validated={params:{},get:{},post:{}}},te=e=>{(e=>{const{controllerModule:t,validateModule:s}=E();console.log("load controller path:",t),S(t,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const r=R(t.wholePath);if(!r)return;const o=Reflect.getMetadata(y,r);o instanceof Map&&0!==o.size&&z(s,t,r,o,(s,r)=>{if(!s)return!0;if(!e)throw new Error(`Route requires auth but no authCheckCallback provided. File: ${t.wholePath}`);return e({filePath:t,ctx:r})})})})(e),$.get("/",async e=>{e.body={code:200,message:"hello koa-ts-cli https://www.npmjs.com/package/koa-ts-core"}})};exports.AuthRouter=function(e,t){return x(e,t,!0)},exports.BaseException=q,exports.INDEX_ROUTE=g,exports.ROUTE_METADATA_KEY=y,exports.Router=function(e,t){return x(e,t,!1)},exports.contextStore=v,exports.createKoaApp=async e=>{const t=J(e),{app:s}=t;if(ee(s,t),te(t.auth.handler),t.swagger?.enabled){V.getInstance().loadDocMetadata();const e=V.getInstance().generateSpec();t.swagger.title&&(e.info.title=t.swagger.title),t.swagger.description&&(e.info.description=t.swagger.description),t.swagger.version&&(e.info.version=t.swagger.version);const r=t.swagger.path||"/swagger-ui";s.use(u.koaSwagger({routePrefix:r,swaggerOptions:{spec:e}})),console.log(`[koa-ts-core] Swagger UI available at ${r}`)}return((e,t,s)=>{const{errorConfig:r,koa2Cors:o,trackConfig:a,bodyParser:n}=s,c={[exports.MiddlewarePhase.AsyncContext]:[Q],[exports.MiddlewarePhase.ErrorHandling]:[(p=r?.handler,async(e,t)=>{try{await t()}catch(t){if(e.hook?.(e,"error"),p)p(t,e);else{const e=t.statusCode||t.status||500,s=t.code??-1;b(e,{message:t.message,code:s,data:"production"!==process.env.NODE_ENV?t.stack:void 0})}}})],[exports.MiddlewarePhase.Logging]:[X(a),Y],[exports.MiddlewarePhase.Security]:[...o?[o(d)]:[]],[exports.MiddlewarePhase.BodyParsing]:[i(n),G],[exports.MiddlewarePhase.Auth]:[],[exports.MiddlewarePhase.Routing]:[$.routes()]};var p;const u=[exports.MiddlewarePhase.AsyncContext,exports.MiddlewarePhase.ErrorHandling,exports.MiddlewarePhase.Logging,exports.MiddlewarePhase.Security,exports.MiddlewarePhase.BodyParsing,exports.MiddlewarePhase.Auth,exports.MiddlewarePhase.Routing];for(const s of u){const r=t.get(s),o=c[s]??[];if(r?.before)for(const t of r.before)e.use(t);for(const t of o)e.use(t);if(r?.use)for(const t of r.use)e.use(t);if(r?.after)for(const t of r.after)e.use(t)}})(s,t.phaseMiddlewares,{errorConfig:t.error,koa2Cors:t.koa2Cors,trackConfig:t.trackId,bodyParser:t.bodyParser}),await _(s),[s,$]},exports.errorRsp=b,exports.getCurrentContext=P,exports.getSrcModulePaths=E,exports.successRsp=e=>{const t=Array.isArray(e?.data);k({success:!0,message:e?.message,data:t?{list:e?.data}:e?.data})},exports.unSuccessRsp=e=>{k({success:!1,errorCode:e?.code,errorMessage:e?.message,data:e?.data})};
|
|
1
|
+
"use strict";require("reflect-metadata");var e=require("async_hooks"),t=require("fs"),s=require("path"),r=require("@koa/router"),o=require("koa"),a=require("portfinder"),n=require("dotenv"),c=require("log4js"),i=require("koa-bodyparser"),d=require("koa2-cors"),p=require("crypto"),l=require("koa2-swagger-ui");const u=["get","post","put","delete","patch","options"],h=0,g="index",f="get_",m=[g,f,...u],y=Symbol("route_metadata");function w(e){const t=e.trim();if(!t)return"";return`/${t.replace(/^\/+/,"")}`}function x(e,t,s=!1){return(r,o,a)=>{if("function"!=typeof a.value)throw new Error(`@Router/@AuthRouter 只能用于方法:${o}`);let n=Reflect.getMetadata(y,r.constructor);n||(n=new Map,Reflect.defineMetadata(y,n,r.constructor));const c=function(e,t){return e||(t===f?"get":u.includes(t)?t:"get")}(e,o),i=function(e,t){return null!=e?w(e):m.includes(t)?"":w(t)}(t,o),d={handler:a.value,path:i,method:c,functionName:o,authRequired:s,middlewares:[],className:r.constructor.name,apiPrePath:""};return n.set(o,d),a}}const v=new e.AsyncLocalStorage,P=()=>{const e=v.getStore();if(!e)throw new Error("context is not exist");return e},k=e=>{const{success:t=!0,errorCode:s=-1,message:r="success",errorMessage:o="error request",data:a=null,statusCode:n=200}=e??{},c=P(),i={code:t?h:s,message:t?r:o,...null!=a?{data:a}:{}};c.trackId&&(i.trackId=c.trackId),c.body=i,c.status=n},b=(e,t)=>{k({success:!1,errorMessage:t?.message,errorCode:t?.code,data:t?.data,statusCode:e})};class q extends Error{constructor(e){super(e?.message||"An unexpected error occurred"),this.name=this.constructor.name,this.code=e?.code??-1,this.statusCode=500,"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.stack=this.stack??new Error(this.message).stack}get toRspOptions(){return{code:this.code,message:this.message,data:this.stack}}}const M=()=>{const e=process.cwd();if(t.existsSync(s.resolve(e,"controller")))return e;const r=s.resolve(e,"src");if(t.existsSync(r))return r;const o=s.resolve(e,"dist");if(t.existsSync(o))return o;const a=s.resolve(e,"build");return t.existsSync(a)?a:r},E=()=>{const e=M();return{controllerModule:s.resolve(e,"controller"),validateModule:s.resolve(e,"validate"),viewModule:s.resolve(e,"view"),docModule:s.resolve(e,"doc")}},S=e=>{if(t.existsSync(e))try{const t=require(e);return t.default||t}catch(t){throw console.error(`[koa-ts-core] Failed to load module: ${e}`,t),t}};function R(e,r){t.existsSync(e)&&t.readdirSync(e,{withFileTypes:!0}).forEach(t=>{const o=s.resolve(e,t.name);t.isFile()?r({path:e,name:t.name,wholePath:o},t):t.isDirectory()&&R(o,r)})}const A=e=>e.split(s.sep).join("/"),I=e=>{const t=A(e).replace(/^\/+/,"");return t?`/${t}`:""},N=(e,t)=>{const s=A(e.path).split("/"),r=s.lastIndexOf(t);if(-1===r){const s=e.path.indexOf(t);if(-1===s)throw new Error(`'${t}' not found in path: ${e.path}`);const r=e.path.substring(s+t.length),o=I(r),a=e.name?.split(".")?.[0];return{pathPrefix:o,moduleName:a,name:e.name}}const o=s.slice(r+1).join("/"),a=I(o),n=e.name?.split(".")?.[0];return{pathPrefix:a,moduleName:n,name:e.name}};function O(e){try{return t.statSync(e).isDirectory()}catch{return!1}}var C;exports.MiddlewarePhase=void 0,(C=exports.MiddlewarePhase||(exports.MiddlewarePhase={})).AsyncContext="AsyncContext",C.ErrorHandling="ErrorHandling",C.Logging="Logging",C.Security="Security",C.BodyParsing="BodyParsing",C.Auth="Auth",C.Routing="Routing";const $=new r,_=async e=>{if(!process.env.APP_PORT)try{const e=await a.getPortPromise({port:8e3,stopPort:9999});process.env.APP_PORT=String(e)}catch(e){console.warn("[koa-ts-core] Failed to find available port automatically.")}const t=e.listen;e.listen=function(...e){if(0===e.length&&process.env.APP_PORT){const t=Number(process.env.APP_PORT);isNaN(t)||e.push(t)}const s=t.apply(this,e);return s.on("listening",()=>(e=>{const t=e.address();if(t)if("string"==typeof t)console.log(`Server listening on ${t}`);else{const{port:e}=t;console.log(`Server running at http://localhost:${e}`)}})(s)),s}},j=Symbol("validate_metadata"),D=Symbol("swagger_tags"),T=Symbol("swagger_operation"),L=Symbol("swagger_parameters"),B=Symbol("swagger_body"),U=Symbol("swagger_responses"),H={DOCUMENTATION_DIR:s.join(process.cwd(),"doc")},F=()=>{const e=[];return O(H.DOCUMENTATION_DIR)?(R(H.DOCUMENTATION_DIR,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const{pathPrefix:s}=N(t,"doc"),r=((e,t)=>{const s=S(e.wholePath);if(!s)return{configs:[],desc:"",name:"",pathPrefix:t,apiPrePath:""};const r=s.prototype,o=Object.getOwnPropertyNames(r).filter(e=>"function"==typeof r[e]&&"constructor"!==e),a=e.path.indexOf("doc"),n=a>=0?I(e.path.substring(a+3)):"";let c=n;if(e.name&&!e.name.startsWith("index.")){const t=e.name.split(".")[0];c=`${n}/${t}`}const i=o.map(e=>{const t=r[e];if("function"==typeof t){const e=t();return e.path=`${c}${e.path}`,e}return null}).filter(e=>!!e);let d="";if(i[0]?.path){const e=i[0].path.lastIndexOf("/");e>-1&&(d=i[0].path.substring(0,e))}return{configs:i,desc:s.desc,name:s.name,pathPrefix:t,apiPrePath:d}})(t,s);r.configs.length>0&&e.push(r)}),e):[]};class V{constructor(){this.paths={}}static getInstance(){return this.instance||(this.instance=new V),this.instance}addRoute(e,t,s,r){const o=r.replace(/:([a-zA-Z0-9_]+)/g,"{$1}");this.paths[o]||(this.paths[o]={});const a=e.prototype,n=Reflect.getMetadata(T,a,t)||{},c=n.summary||t,i=n.description,d=Reflect.getMetadata(D,e)||[e.name],p=Reflect.getMetadata(L,a,t)||[],l=Reflect.getMetadata(B,a,t);let u;l&&(u={description:l.description,required:l.required,content:{"application/json":{schema:l.schema||{type:"object"}}}});const h=Reflect.getMetadata(U,a,t)||[],g={};h.forEach(e=>{g[e.status]={description:e.description||"",content:e.schema?{"application/json":{schema:e.schema}}:void 0}}),0===Object.keys(g).length&&(g[200]={description:"Success"}),this.paths[o][s.toLowerCase()]={tags:d,summary:c,description:i,parameters:p,requestBody:u,responses:g}}loadDocMetadata(){F().forEach(e=>{const t=e.desc||e.name;e.configs.forEach(e=>{const s=("/"+e.path).replace(/\/+/g,"/"),r=s.replace(/:([a-zA-Z0-9_]+)/g,"{$1}"),o=e.method.toLowerCase();this.paths[r]||(this.paths[r]={}),this.paths[r][o]||(this.paths[r][o]={});const a=this.paths[r][o];a.summary||(a.summary=e.description),a.tags&&0!==a.tags.length||(a.tags=[t]);const n=s.match(/:([a-zA-Z0-9_]+)/g);if(n&&(a.parameters||(a.parameters=[]),n.forEach(e=>{const t=e.substring(1);a.parameters.find(e=>e.name===t&&"path"===e.in)||a.parameters.push({name:t,in:"path",required:!0,schema:{type:"string"},description:t})})),e.request?.query&&(a.parameters||(a.parameters=[]),Object.keys(e.request.query).forEach(t=>{if(!a.parameters.find(e=>e.name===t&&"query"===e.in)){const s=e.request?.query?.[t];s&&"object"==typeof s&&a.parameters.push({name:t,in:"query",description:s.description,required:!1!==s.required,schema:s.schema??{type:s.type??"string"},example:s.example})}})),e.request?.header&&(a.parameters||(a.parameters=[]),Object.keys(e.request.header).forEach(t=>{if("content-type"===t.toLowerCase())return;if(!a.parameters.find(e=>e.name===t&&"header"===e.in)){const s=e.request?.header?.[t];s&&"object"==typeof s&&a.parameters.push({name:t,in:"header",description:s.description,required:!1!==s.required,schema:s.schema??{type:s.type??"string"},example:s.example})}})),e.request?.body&&Object.keys(e.request.body).length>0&&!a.requestBody){const t=e.request.body;a.requestBody={description:t.description,required:t.required,content:{"application/json":{schema:t.schema||{type:"object"},example:t.example}}}}e.response?.body&&(a.responses||(a.responses={}),a.responses[200]||(a.responses[200]={description:"Success",content:{"application/json":{schema:{type:"object",example:e.response.body}}}}))})})}tagSpecByPrefix(e,t={}){const{level:s=2,onlyIfEmpty:r=!0}=t,o=new Set(["get","post","put","delete","patch","head","options","trace"]),a=new Set((e.tags||[]).map(e=>e.name)),n=e.paths||{};for(const[e,t]of Object.entries(n)){if(!t)continue;const n="/"+e.split("/").filter(Boolean).slice(0,s).join("/");for(const[e,s]of Object.entries(t)){if(!o.has(e))continue;if(!s||"object"!=typeof s)continue;const t=s,c=Array.isArray(t.tags)&&t.tags.length>0;r&&c||(t.tags=[n]),a.add(n)}}return e.tags=Array.from(a).map(e=>({name:e})),e}generateSpec(){return{openapi:"3.0.0",info:{title:"API Documentation",version:"1.0.0",description:"Auto generated by koa-ts-core"},paths:this.paths,components:{schemas:{}}}}}const z=(e,t,s,r,o)=>{if(!r||0===r.size)return;const{pathPrefix:a,moduleName:n,name:c}=N(t,"controller"),i=`${a}/${n}`,d=S(`${e}${a}/${c}`),p=e=>async(t,s)=>{const r=e(t);r instanceof Promise&&await r,await s()};r.forEach(e=>{const{method:t,path:r,handler:n,functionName:c,authRequired:l,middlewares:u}=e,h=[...u],g=Reflect.getMetadata(j,s.prototype,c);g?h.push(p(g)):d&&"function"==typeof d[c]&&h.push(p(d[c].bind(d))),h.push(((e,t)=>async(s,r)=>{let a=o(e,s);return a instanceof Promise&&(a=await a),a?t(s,r):s.throw(403,"无权限")})(l,n));const f=W(r,i,c,h,t);V.getInstance().addRoute(s,c,t,f),e.path=f,e.apiPrePath=a})},W=(e,t,s,r,o)=>{const a=`${t}${e}`;if(s===g){const e=`${t}/index`;$[o](e,...r)}return $[o](a,...r),a};let Z=null;const K=()=>(Z||(Z=c.configure({appenders:{console:{type:"console"},file:{type:"dateFile",filename:"logs/app.log",pattern:".yyyy-MM-dd",compress:!0,numBackups:7,alwaysIncludePattern:!0}},categories:{default:{appenders:["console","file"],level:"info"}}})),Z),J=(e,t)=>v.run(e,()=>t()),Q=e=>{const{generator:t,exposeHeader:s=!0,headerName:r="x-request-id"}=e;return!1===t?async(e,t)=>{await t()}:async(e,o)=>{const a=await t(e);a&&(e.trackId=a,s&&e.set(r,a)),await o()}},X=async(e,t)=>{const s=Date.now(),r=K().getLogger(),o=e.runtimeLog;o&&r.info("[REQUEST] %s %s %s - ip: %s - ua: %s",e.trackId,e.method,e.url,e.ip,e.get("user-agent")||"-");try{e.hook?.(e,"request"),await t(),e.hook?.(e,"response")}catch(t){const a=Date.now()-s;throw o&&r.error("[ERROR] %s %s %s - status: %s - cost: %dms - message: %s - stack: %s",e.trackId,e.method,e.url,t.status||500,a,t.message||"unknown error",t.stack||""),t}const a=Date.now()-s;o&&r.info("[RESPONSE] %s %s %s - status: %d - cost: %dms - length: %s",e.trackId,e.method,e.url,e.status,a,e.length||e.response.length||"-")},Y=async(e,t)=>{e.extra={get:e.query,post:e.request.body??{}},await t()};(()=>{const e=process.env.NODE_ENV||"production";let t;const r=process.env.ENV_DIR;if(r){if(t=s.isAbsolute(r)?r:s.resolve(process.cwd(),r),!O(t))throw new Error(`[koa-ts-core] ENV_DIR is set but directory not found: ${t}`)}else{const e=M();let r=s.resolve(e,"env");if(O(r)||(r=s.resolve(e,"..","env")),!O(r))return void console.warn(`[koa-ts-core] env directory not found: tried ${s.resolve(e,"env")} and ${s.resolve(e,"..","env")}`);t=r}console.log("[koa-ts-core] load env path:",t),n.config({path:s.resolve(t,".env.local")});const o={development:".development.env",test:".test.env",production:".production.env"}[e];o&&n.config({path:s.resolve(t,`${o}.local`)}),o&&n.config({path:s.resolve(t,o)}),n.config({path:s.resolve(t,".common.env")})})(),global.isDev="development"===process.env.NODE_ENV;const G=e=>{const{koaInstance:t,auth:s,error:r,log:a,hooks:n,phaseMiddlewares:c,koa2Cors:i,trackId:d,swagger:l,bodyParser:u}=e;return{app:t??new o,auth:{handler:s?.handler},error:{handler:r?.handler,exposeStack:r?.exposeStack??"production"!==process.env.NODE_ENV},log:{log4:a?.log4??!1,runtimeLog:a?.runtimeLog??!0},hooks:{register:n?.register},phaseMiddlewares:c??new Map,koa2Cors:i,trackId:{generator:!1!==d?.generator&&(async e=>{const t=d?.headerName??"x-request-id",s=e.get(t);return s||p.randomUUID()}),exposeHeader:d?.exposeHeader??!0,headerName:d?.headerName??"x-request-id"},swagger:l,bodyParser:u}},ee=(e,t)=>{const{log:s,hooks:r}=t;e.context.log4=(e=>{if(!1!==e)return"boolean"==typeof e&&!0===e?K():e(c)})(s.log4),e.context.runtimeLog=s.runtimeLog,e.context.hook=r.register,e.context.validated={params:{},get:{},post:{}}},te=e=>{(e=>{const{controllerModule:t,validateModule:s}=E();console.log("load controller path:",t),R(t,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const r=S(t.wholePath);if(!r)return;const o=Reflect.getMetadata(y,r);o instanceof Map&&0!==o.size&&z(s,t,r,o,(s,r)=>{if(!s)return!0;if(!e)throw new Error(`Route requires auth but no authCheckCallback provided. File: ${t.wholePath}`);return e({filePath:t,ctx:r})})})})(e),$.get("/",async e=>{e.body={code:200,message:"hello koa-ts-cli https://www.npmjs.com/package/koa-ts-core"}})};exports.AuthRouter=function(e,t){return x(e,t,!0)},exports.BaseException=q,exports.INDEX_ROUTE=g,exports.ROUTE_METADATA_KEY=y,exports.Router=function(e,t){return x(e,t,!1)},exports.contextStore=v,exports.createKoaApp=async e=>{const t=G(e),{app:s}=t;if(ee(s,t),te(t.auth.handler),t.swagger?.enabled){V.getInstance().loadDocMetadata();const e=V.getInstance().generateSpec();t.swagger.title&&(e.info.title=t.swagger.title),t.swagger.description&&(e.info.description=t.swagger.description),t.swagger.version&&(e.info.version=t.swagger.version);const r=t.swagger.path||"/swagger-ui";console.log(JSON.stringify(V.getInstance().tagSpecByPrefix(e,{level:2}))),s.use(l.koaSwagger({routePrefix:r,swaggerOptions:{spec:V.getInstance().tagSpecByPrefix(e,{level:2,onlyIfEmpty:!1})}})),console.log(`[koa-ts-core] Swagger UI available at ${r}`)}return((e,t,s)=>{const{errorConfig:r,koa2Cors:o,trackConfig:a,bodyParser:n}=s,c={[exports.MiddlewarePhase.AsyncContext]:[J],[exports.MiddlewarePhase.ErrorHandling]:[(p=r?.handler,async(e,t)=>{try{await t()}catch(t){if(e.hook?.(e,"error"),p)p(t,e);else{const e=t.statusCode||t.status||500,s=t.code??-1;b(e,{message:t.message,code:s,data:"production"!==process.env.NODE_ENV?t.stack:void 0})}}})],[exports.MiddlewarePhase.Logging]:[Q(a),X],[exports.MiddlewarePhase.Security]:[...o?[o(d)]:[]],[exports.MiddlewarePhase.BodyParsing]:[i(n),Y],[exports.MiddlewarePhase.Auth]:[],[exports.MiddlewarePhase.Routing]:[$.routes()]};var p;const l=[exports.MiddlewarePhase.AsyncContext,exports.MiddlewarePhase.ErrorHandling,exports.MiddlewarePhase.Logging,exports.MiddlewarePhase.Security,exports.MiddlewarePhase.BodyParsing,exports.MiddlewarePhase.Auth,exports.MiddlewarePhase.Routing];for(const s of l){const r=t.get(s),o=c[s]??[];if(r?.before)for(const t of r.before)e.use(t);for(const t of o)e.use(t);if(r?.use)for(const t of r.use)e.use(t);if(r?.after)for(const t of r.after)e.use(t)}})(s,t.phaseMiddlewares,{errorConfig:t.error,koa2Cors:t.koa2Cors,trackConfig:t.trackId,bodyParser:t.bodyParser}),await _(s),[s,$]},exports.errorRsp=b,exports.getCurrentContext=P,exports.getSrcModulePaths=E,exports.successRsp=e=>{const t=Array.isArray(e?.data);k({success:!0,message:e?.message,data:t?{list:e?.data}:e?.data})},exports.unSuccessRsp=e=>{k({success:!1,errorCode:e?.code,errorMessage:e?.message,data:e?.data})};
|
package/dist/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"reflect-metadata";import{AsyncLocalStorage as e}from"async_hooks";import{statSync as t,existsSync as o,readdirSync as r}from"fs";import{resolve as s,sep as a,join as n,isAbsolute as c}from"path";import i from"@koa/router";import d from"koa";import p from"portfinder";import u from"dotenv";import l from"log4js";import h from"koa-bodyparser";import g from"koa2-cors";import{randomUUID as m}from"crypto";import{koaSwagger as f}from"koa2-swagger-ui";const y=["get","post","put","delete","patch","options"],w=0,k="index",b="get_",P=[k,b,...y],v=Symbol("route_metadata");function x(e){const t=e.trim();if(!t)return"";return`/${t.replace(/^\/+/,"")}`}function E(e,t,o=!1){return(r,s,a)=>{if("function"!=typeof a.value)throw new Error(`@Router/@AuthRouter 只能用于方法:${s}`);let n=Reflect.getMetadata(v,r.constructor);n||(n=new Map,Reflect.defineMetadata(v,n,r.constructor));const c=function(e,t){return e||(t===b?"get":y.includes(t)?t:"get")}(e,s),i=function(e,t){return null!=e?x(e):P.includes(t)?"":x(t)}(t,s),d={handler:a.value,path:i,method:c,functionName:s,authRequired:o,middlewares:[],className:r.constructor.name,apiPrePath:""};return n.set(s,d),a}}function q(e,t){return E(e,t,!1)}function R(e,t){return E(e,t,!0)}const M=new e,N=()=>{const e=M.getStore();if(!e)throw new Error("context is not exist");return e},C=e=>{const{success:t=!0,errorCode:o=-1,message:r="success",errorMessage:s="error request",data:a=null,statusCode:n=200}=e??{},c=N(),i={code:t?w:o,message:t?r:s,...null!=a?{data:a}:{}};c.trackId&&(i.trackId=c.trackId),c.body=i,c.status=n},I=e=>{const t=Array.isArray(e?.data);C({success:!0,message:e?.message,data:t?{list:e?.data}:e?.data})},O=e=>{C({success:!1,errorCode:e?.code,errorMessage:e?.message,data:e?.data})},$=(e,t)=>{C({success:!1,errorMessage:t?.message,errorCode:t?.code,data:t?.data,statusCode:e})};class S extends Error{constructor(e){super(e?.message||"An unexpected error occurred"),this.name=this.constructor.name,this.code=e?.code??-1,this.statusCode=500,"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.stack=this.stack??new Error(this.message).stack}get toRspOptions(){return{code:this.code,message:this.message,data:this.stack}}}const _=()=>{const e=process.cwd();if(o(s(e,"controller")))return e;const t=s(e,"src");if(o(t))return t;const r=s(e,"dist");if(o(r))return r;const a=s(e,"build");return o(a)?a:t},A=()=>{const e=_();return{controllerModule:s(e,"controller"),validateModule:s(e,"validate"),viewModule:s(e,"view"),docModule:s(e,"doc")}},D=e=>{if(o(e))try{const t=require(e);return t.default||t}catch(t){throw console.error(`[koa-ts-core] Failed to load module: ${e}`,t),t}};function j(e,t){o(e)&&r(e,{withFileTypes:!0}).forEach(o=>{const r=s(e,o.name);o.isFile()?t({path:e,name:o.name,wholePath:r},o):o.isDirectory()&&j(r,t)})}const T=e=>e.split(a).join("/"),L=e=>{const t=T(e).replace(/^\/+/,"");return t?`/${t}`:""},B=(e,t)=>{const o=T(e.path).split("/"),r=o.lastIndexOf(t);if(-1===r){const o=e.path.indexOf(t);if(-1===o)throw new Error(`'${t}' not found in path: ${e.path}`);const r=e.path.substring(o+t.length),s=L(r),a=e.name?.split(".")?.[0];return{pathPrefix:s,moduleName:a,name:e.name}}const s=o.slice(r+1).join("/"),a=L(s),n=e.name?.split(".")?.[0];return{pathPrefix:a,moduleName:n,name:e.name}};function H(e){try{return t(e).isDirectory()}catch{return!1}}var F;!function(e){e.AsyncContext="AsyncContext",e.ErrorHandling="ErrorHandling",e.Logging="Logging",e.Security="Security",e.BodyParsing="BodyParsing",e.Auth="Auth",e.Routing="Routing"}(F||(F={}));const V=new i,z=async e=>{if(!process.env.APP_PORT)try{const e=await p.getPortPromise({port:8e3,stopPort:9999});process.env.APP_PORT=String(e)}catch(e){console.warn("[koa-ts-core] Failed to find available port automatically.")}const t=e.listen;e.listen=function(...e){if(0===e.length&&process.env.APP_PORT){const t=Number(process.env.APP_PORT);isNaN(t)||e.push(t)}const o=t.apply(this,e);return o.on("listening",()=>(e=>{const t=e.address();if(t)if("string"==typeof t)console.log(`Server listening on ${t}`);else{const{port:e}=t;console.log(`Server running at http://localhost:${e}`)}})(o)),o}},U=Symbol("validate_metadata"),W=Symbol("swagger_tags"),Z=Symbol("swagger_operation"),Q=Symbol("swagger_parameters"),G=Symbol("swagger_body"),J=Symbol("swagger_responses"),K={DOCUMENTATION_DIR:n(process.cwd(),"doc")},X=()=>{const e=[];return H(K.DOCUMENTATION_DIR)?(j(K.DOCUMENTATION_DIR,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const{pathPrefix:o}=B(t,"doc"),r=((e,t)=>{const o=D(e.wholePath);if(!o)return{configs:[],desc:"",name:"",pathPrefix:t,apiPrePath:""};const r=o.prototype,s=Object.getOwnPropertyNames(r).filter(e=>"function"==typeof r[e]&&"constructor"!==e),a=e.path.indexOf("doc"),n=a>=0?L(e.path.substring(a+3)):"";let c=n;if(e.name&&!e.name.startsWith("index.")){const t=e.name.split(".")[0];c=`${n}/${t}`}const i=s.map(e=>{const t=r[e];if("function"==typeof t){const e=t();return e.path=`${c}${e.path}`,e}return null}).filter(e=>!!e);let d="";if(i[0]?.path){const e=i[0].path.lastIndexOf("/");e>-1&&(d=i[0].path.substring(0,e))}return{configs:i,desc:o.desc,name:o.name,pathPrefix:t,apiPrePath:d}})(t,o);r.configs.length>0&&e.push(r)}),e):[]};class Y{constructor(){this.paths={}}static getInstance(){return this.instance||(this.instance=new Y),this.instance}addRoute(e,t,o,r){const s=r.replace(/:([a-zA-Z0-9_]+)/g,"{$1}");this.paths[s]||(this.paths[s]={});const a=e.prototype,n=Reflect.getMetadata(Z,a,t)||{},c=n.summary||t,i=n.description,d=Reflect.getMetadata(W,e)||[e.name],p=Reflect.getMetadata(Q,a,t)||[],u=Reflect.getMetadata(G,a,t);let l;u&&(l={description:u.description,required:u.required,content:{"application/json":{schema:u.schema||{type:"object"}}}});const h=Reflect.getMetadata(J,a,t)||[],g={};h.forEach(e=>{g[e.status]={description:e.description||"",content:e.schema?{"application/json":{schema:e.schema}}:void 0}}),0===Object.keys(g).length&&(g[200]={description:"Success"}),this.paths[s][o.toLowerCase()]={tags:d,summary:c,description:i,parameters:p,requestBody:l,responses:g}}loadDocMetadata(){X().forEach(e=>{const t=e.desc||e.name;e.configs.forEach(e=>{const o=("/"+e.path).replace(/\/+/g,"/"),r=o.replace(/:([a-zA-Z0-9_]+)/g,"{$1}"),s=e.method.toLowerCase();this.paths[r]||(this.paths[r]={}),this.paths[r][s]||(this.paths[r][s]={});const a=this.paths[r][s];a.summary||(a.summary=e.description),a.tags&&0!==a.tags.length||(a.tags=[t]);const n=o.match(/:([a-zA-Z0-9_]+)/g);if(n&&(a.parameters||(a.parameters=[]),n.forEach(e=>{const t=e.substring(1);a.parameters.find(e=>e.name===t&&"path"===e.in)||a.parameters.push({name:t,in:"path",required:!0,schema:{type:"string"},description:t})})),e.request?.query&&(a.parameters||(a.parameters=[]),Object.keys(e.request.query).forEach(t=>{if(!a.parameters.find(e=>e.name===t&&"query"===e.in)){const o=e.request.query[t];o&&"object"==typeof o&&a.parameters.push({name:t,in:"query",description:o.description,required:!1!==o.required,schema:o.schema??{type:o.type??"string"},example:o.example})}})),e.request?.header&&(a.parameters||(a.parameters=[]),Object.keys(e.request.header).forEach(t=>{if("content-type"===t.toLowerCase())return;if(!a.parameters.find(e=>e.name===t&&"header"===e.in)){const o=e.request.header[t];o&&"object"==typeof o&&a.parameters.push({name:t,in:"header",description:o.description,required:!1!==o.required,schema:o.schema??{type:o.type??"string"},example:o.example})}})),e.request?.body&&Object.keys(e.request.body).length>0&&!a.requestBody){const t=e.request.body;a.requestBody={description:t.description,required:t.required,content:{"application/json":{schema:t.schema||{type:"object"},example:t.example}}}}e.response?.body&&(a.responses||(a.responses={}),a.responses[200]||(a.responses[200]={description:"Success",content:{"application/json":{schema:{type:"object",example:e.response.body}}}}))})})}generateSpec(){return{openapi:"3.0.0",info:{title:"API Documentation",version:"1.0.0",description:"Auto generated by koa-ts-core"},paths:this.paths,components:{schemas:{}}}}}const ee=(e,t,o,r,s)=>{if(!r||0===r.size)return;const{pathPrefix:a,moduleName:n,name:c}=B(t,"controller"),i=`${a}/${n}`,d=D(`${e}${a}/${c}`),p=e=>async(t,o)=>{const r=e(t);r instanceof Promise&&await r,await o()};r.forEach(e=>{const{method:t,path:r,handler:n,functionName:c,authRequired:u,middlewares:l}=e,h=[...l],g=Reflect.getMetadata(U,o.prototype,c);g?h.push(p(g)):d&&"function"==typeof d[c]&&h.push(p(d[c].bind(d))),h.push(((e,t)=>async(o,r)=>{let a=s(e,o);return a instanceof Promise&&(a=await a),a?t(o,r):o.throw(403,"无权限")})(u,n));const m=te(r,i,c,h,t);Y.getInstance().addRoute(o,c,t,m),e.path=m,e.apiPrePath=a})},te=(e,t,o,r,s)=>{const a=`${t}${e}`;if(o===k){const e=`${t}/index`;V[s](e,...r)}return V[s](a,...r),a};let oe=null;const re=()=>(oe||(oe=l.configure({appenders:{console:{type:"console"},file:{type:"dateFile",filename:"logs/app.log",pattern:".yyyy-MM-dd",compress:!0,numBackups:7,alwaysIncludePattern:!0}},categories:{default:{appenders:["console","file"],level:"info"}}})),oe),se=(e,t)=>M.run(e,()=>t()),ae=e=>{const{generator:t,exposeHeader:o=!0,headerName:r="x-request-id"}=e;return!1===t?async(e,t)=>{await t()}:async(e,s)=>{const a=await t(e);a&&(e.trackId=a,o&&e.set(r,a)),await s()}},ne=async(e,t)=>{const o=Date.now(),r=re().getLogger(),s=e.runtimeLog;s&&r.info("[REQUEST] %s %s %s - ip: %s - ua: %s",e.trackId,e.method,e.url,e.ip,e.get("user-agent")||"-");try{e.hook?.(e,"request"),await t(),e.hook?.(e,"response")}catch(t){const a=Date.now()-o;throw s&&r.error("[ERROR] %s %s %s - status: %s - cost: %dms - message: %s - stack: %s",e.trackId,e.method,e.url,t.status||500,a,t.message||"unknown error",t.stack||""),t}const a=Date.now()-o;s&&r.info("[RESPONSE] %s %s %s - status: %d - cost: %dms - length: %s",e.trackId,e.method,e.url,e.status,a,e.length||e.response.length||"-")},ce=async(e,t)=>{e.extra={get:e.query,post:e.request.body??{}},await t()};(()=>{const e=process.env.NODE_ENV||"production";let t;const o=process.env.ENV_DIR;if(o){if(t=c(o)?o:s(process.cwd(),o),!H(t))throw new Error(`[koa-ts-core] ENV_DIR is set but directory not found: ${t}`)}else{const e=_();let o=s(e,"env");if(H(o)||(o=s(e,"..","env")),!H(o))return void console.warn(`[koa-ts-core] env directory not found: tried ${s(e,"env")} and ${s(e,"..","env")}`);t=o}console.log("[koa-ts-core] load env path:",t),u.config({path:s(t,".env.local")});const r={development:".development.env",test:".test.env",production:".production.env"}[e];r&&u.config({path:s(t,`${r}.local`)}),r&&u.config({path:s(t,r)}),u.config({path:s(t,".common.env")})})(),global.isDev="development"===process.env.NODE_ENV;const ie=e=>{const{koaInstance:t,auth:o,error:r,log:s,hooks:a,phaseMiddlewares:n,koa2Cors:c,trackId:i,swagger:p,bodyParser:u}=e;return{app:t??new d,auth:{handler:o?.handler},error:{handler:r?.handler,exposeStack:r?.exposeStack??"production"!==process.env.NODE_ENV},log:{log4:s?.log4??!1,runtimeLog:s?.runtimeLog??!0},hooks:{register:a?.register},phaseMiddlewares:n??new Map,koa2Cors:c,trackId:{generator:!1!==i?.generator&&(async e=>{const t=i?.headerName??"x-request-id",o=e.get(t);return o||m()}),exposeHeader:i?.exposeHeader??!0,headerName:i?.headerName??"x-request-id"},swagger:p,bodyParser:u}},de=(e,t)=>{const{log:o,hooks:r}=t;e.context.log4=(e=>{if(!1!==e)return"boolean"==typeof e&&!0===e?re():e(l)})(o.log4),e.context.runtimeLog=o.runtimeLog,e.context.hook=r.register,e.context.validated={params:{},get:{},post:{}}},pe=e=>{(e=>{const{controllerModule:t,validateModule:o}=A();console.log("load controller path:",t),j(t,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const r=D(t.wholePath);if(!r)return;const s=Reflect.getMetadata(v,r);s instanceof Map&&0!==s.size&&ee(o,t,r,s,(o,r)=>{if(!o)return!0;if(!e)throw new Error(`Route requires auth but no authCheckCallback provided. File: ${t.wholePath}`);return e({filePath:t,ctx:r})})})})(e),V.get("/",async e=>{e.body={code:200,message:"hello koa-ts-cli https://www.npmjs.com/package/koa-ts-core"}})},ue=async e=>{const t=ie(e),{app:o}=t;if(de(o,t),pe(t.auth.handler),t.swagger?.enabled){Y.getInstance().loadDocMetadata();const e=Y.getInstance().generateSpec();t.swagger.title&&(e.info.title=t.swagger.title),t.swagger.description&&(e.info.description=t.swagger.description),t.swagger.version&&(e.info.version=t.swagger.version);const r=t.swagger.path||"/swagger-ui";o.use(f({routePrefix:r,swaggerOptions:{spec:e}})),console.log(`[koa-ts-core] Swagger UI available at ${r}`)}return((e,t,o)=>{const{errorConfig:r,koa2Cors:s,trackConfig:a,bodyParser:n}=o,c={[F.AsyncContext]:[se],[F.ErrorHandling]:[(i=r?.handler,async(e,t)=>{try{await t()}catch(t){if(e.hook?.(e,"error"),i)i(t,e);else{const e=t.statusCode||t.status||500,o=t.code??-1;$(e,{message:t.message,code:o,data:"production"!==process.env.NODE_ENV?t.stack:void 0})}}})],[F.Logging]:[ae(a),ne],[F.Security]:[...s?[s(g)]:[]],[F.BodyParsing]:[h(n),ce],[F.Auth]:[],[F.Routing]:[V.routes()]};var i;const d=[F.AsyncContext,F.ErrorHandling,F.Logging,F.Security,F.BodyParsing,F.Auth,F.Routing];for(const o of d){const r=t.get(o),s=c[o]??[];if(r?.before)for(const t of r.before)e.use(t);for(const t of s)e.use(t);if(r?.use)for(const t of r.use)e.use(t);if(r?.after)for(const t of r.after)e.use(t)}})(o,t.phaseMiddlewares,{errorConfig:t.error,koa2Cors:t.koa2Cors,trackConfig:t.trackId,bodyParser:t.bodyParser}),await z(o),[o,V]};export{R as AuthRouter,S as BaseException,k as INDEX_ROUTE,F as MiddlewarePhase,v as ROUTE_METADATA_KEY,q as Router,M as contextStore,ue as createKoaApp,$ as errorRsp,N as getCurrentContext,A as getSrcModulePaths,I as successRsp,O as unSuccessRsp};
|
|
1
|
+
import"reflect-metadata";import{AsyncLocalStorage as e}from"async_hooks";import{statSync as t,existsSync as o,readdirSync as r}from"fs";import{resolve as s,sep as a,join as n,isAbsolute as c}from"path";import i from"@koa/router";import p from"koa";import d from"portfinder";import u from"dotenv";import l from"log4js";import g from"koa-bodyparser";import h from"koa2-cors";import{randomUUID as m}from"crypto";import{koaSwagger as f}from"koa2-swagger-ui";const y=["get","post","put","delete","patch","options"],w=0,k="index",b="get_",P=[k,b,...y],v=Symbol("route_metadata");function x(e){const t=e.trim();if(!t)return"";return`/${t.replace(/^\/+/,"")}`}function E(e,t,o=!1){return(r,s,a)=>{if("function"!=typeof a.value)throw new Error(`@Router/@AuthRouter 只能用于方法:${s}`);let n=Reflect.getMetadata(v,r.constructor);n||(n=new Map,Reflect.defineMetadata(v,n,r.constructor));const c=function(e,t){return e||(t===b?"get":y.includes(t)?t:"get")}(e,s),i=function(e,t){return null!=e?x(e):P.includes(t)?"":x(t)}(t,s),p={handler:a.value,path:i,method:c,functionName:s,authRequired:o,middlewares:[],className:r.constructor.name,apiPrePath:""};return n.set(s,p),a}}function q(e,t){return E(e,t,!1)}function R(e,t){return E(e,t,!0)}const S=new e,I=()=>{const e=S.getStore();if(!e)throw new Error("context is not exist");return e},N=e=>{const{success:t=!0,errorCode:o=-1,message:r="success",errorMessage:s="error request",data:a=null,statusCode:n=200}=e??{},c=I(),i={code:t?w:o,message:t?r:s,...null!=a?{data:a}:{}};c.trackId&&(i.trackId=c.trackId),c.body=i,c.status=n},M=e=>{const t=Array.isArray(e?.data);N({success:!0,message:e?.message,data:t?{list:e?.data}:e?.data})},O=e=>{N({success:!1,errorCode:e?.code,errorMessage:e?.message,data:e?.data})},C=(e,t)=>{N({success:!1,errorMessage:t?.message,errorCode:t?.code,data:t?.data,statusCode:e})};class $ extends Error{constructor(e){super(e?.message||"An unexpected error occurred"),this.name=this.constructor.name,this.code=e?.code??-1,this.statusCode=500,"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.stack=this.stack??new Error(this.message).stack}get toRspOptions(){return{code:this.code,message:this.message,data:this.stack}}}const A=()=>{const e=process.cwd();if(o(s(e,"controller")))return e;const t=s(e,"src");if(o(t))return t;const r=s(e,"dist");if(o(r))return r;const a=s(e,"build");return o(a)?a:t},_=()=>{const e=A();return{controllerModule:s(e,"controller"),validateModule:s(e,"validate"),viewModule:s(e,"view"),docModule:s(e,"doc")}},j=e=>{if(o(e))try{const t=require(e);return t.default||t}catch(t){throw console.error(`[koa-ts-core] Failed to load module: ${e}`,t),t}};function D(e,t){o(e)&&r(e,{withFileTypes:!0}).forEach(o=>{const r=s(e,o.name);o.isFile()?t({path:e,name:o.name,wholePath:r},o):o.isDirectory()&&D(r,t)})}const T=e=>e.split(a).join("/"),L=e=>{const t=T(e).replace(/^\/+/,"");return t?`/${t}`:""},B=(e,t)=>{const o=T(e.path).split("/"),r=o.lastIndexOf(t);if(-1===r){const o=e.path.indexOf(t);if(-1===o)throw new Error(`'${t}' not found in path: ${e.path}`);const r=e.path.substring(o+t.length),s=L(r),a=e.name?.split(".")?.[0];return{pathPrefix:s,moduleName:a,name:e.name}}const s=o.slice(r+1).join("/"),a=L(s),n=e.name?.split(".")?.[0];return{pathPrefix:a,moduleName:n,name:e.name}};function H(e){try{return t(e).isDirectory()}catch{return!1}}var F;!function(e){e.AsyncContext="AsyncContext",e.ErrorHandling="ErrorHandling",e.Logging="Logging",e.Security="Security",e.BodyParsing="BodyParsing",e.Auth="Auth",e.Routing="Routing"}(F||(F={}));const V=new i,z=async e=>{if(!process.env.APP_PORT)try{const e=await d.getPortPromise({port:8e3,stopPort:9999});process.env.APP_PORT=String(e)}catch(e){console.warn("[koa-ts-core] Failed to find available port automatically.")}const t=e.listen;e.listen=function(...e){if(0===e.length&&process.env.APP_PORT){const t=Number(process.env.APP_PORT);isNaN(t)||e.push(t)}const o=t.apply(this,e);return o.on("listening",()=>(e=>{const t=e.address();if(t)if("string"==typeof t)console.log(`Server listening on ${t}`);else{const{port:e}=t;console.log(`Server running at http://localhost:${e}`)}})(o)),o}},U=Symbol("validate_metadata"),W=Symbol("swagger_tags"),Z=Symbol("swagger_operation"),J=Symbol("swagger_parameters"),Q=Symbol("swagger_body"),G=Symbol("swagger_responses"),K={DOCUMENTATION_DIR:n(process.cwd(),"doc")},X=()=>{const e=[];return H(K.DOCUMENTATION_DIR)?(D(K.DOCUMENTATION_DIR,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const{pathPrefix:o}=B(t,"doc"),r=((e,t)=>{const o=j(e.wholePath);if(!o)return{configs:[],desc:"",name:"",pathPrefix:t,apiPrePath:""};const r=o.prototype,s=Object.getOwnPropertyNames(r).filter(e=>"function"==typeof r[e]&&"constructor"!==e),a=e.path.indexOf("doc"),n=a>=0?L(e.path.substring(a+3)):"";let c=n;if(e.name&&!e.name.startsWith("index.")){const t=e.name.split(".")[0];c=`${n}/${t}`}const i=s.map(e=>{const t=r[e];if("function"==typeof t){const e=t();return e.path=`${c}${e.path}`,e}return null}).filter(e=>!!e);let p="";if(i[0]?.path){const e=i[0].path.lastIndexOf("/");e>-1&&(p=i[0].path.substring(0,e))}return{configs:i,desc:o.desc,name:o.name,pathPrefix:t,apiPrePath:p}})(t,o);r.configs.length>0&&e.push(r)}),e):[]};class Y{constructor(){this.paths={}}static getInstance(){return this.instance||(this.instance=new Y),this.instance}addRoute(e,t,o,r){const s=r.replace(/:([a-zA-Z0-9_]+)/g,"{$1}");this.paths[s]||(this.paths[s]={});const a=e.prototype,n=Reflect.getMetadata(Z,a,t)||{},c=n.summary||t,i=n.description,p=Reflect.getMetadata(W,e)||[e.name],d=Reflect.getMetadata(J,a,t)||[],u=Reflect.getMetadata(Q,a,t);let l;u&&(l={description:u.description,required:u.required,content:{"application/json":{schema:u.schema||{type:"object"}}}});const g=Reflect.getMetadata(G,a,t)||[],h={};g.forEach(e=>{h[e.status]={description:e.description||"",content:e.schema?{"application/json":{schema:e.schema}}:void 0}}),0===Object.keys(h).length&&(h[200]={description:"Success"}),this.paths[s][o.toLowerCase()]={tags:p,summary:c,description:i,parameters:d,requestBody:l,responses:h}}loadDocMetadata(){X().forEach(e=>{const t=e.desc||e.name;e.configs.forEach(e=>{const o=("/"+e.path).replace(/\/+/g,"/"),r=o.replace(/:([a-zA-Z0-9_]+)/g,"{$1}"),s=e.method.toLowerCase();this.paths[r]||(this.paths[r]={}),this.paths[r][s]||(this.paths[r][s]={});const a=this.paths[r][s];a.summary||(a.summary=e.description),a.tags&&0!==a.tags.length||(a.tags=[t]);const n=o.match(/:([a-zA-Z0-9_]+)/g);if(n&&(a.parameters||(a.parameters=[]),n.forEach(e=>{const t=e.substring(1);a.parameters.find(e=>e.name===t&&"path"===e.in)||a.parameters.push({name:t,in:"path",required:!0,schema:{type:"string"},description:t})})),e.request?.query&&(a.parameters||(a.parameters=[]),Object.keys(e.request.query).forEach(t=>{if(!a.parameters.find(e=>e.name===t&&"query"===e.in)){const o=e.request?.query?.[t];o&&"object"==typeof o&&a.parameters.push({name:t,in:"query",description:o.description,required:!1!==o.required,schema:o.schema??{type:o.type??"string"},example:o.example})}})),e.request?.header&&(a.parameters||(a.parameters=[]),Object.keys(e.request.header).forEach(t=>{if("content-type"===t.toLowerCase())return;if(!a.parameters.find(e=>e.name===t&&"header"===e.in)){const o=e.request?.header?.[t];o&&"object"==typeof o&&a.parameters.push({name:t,in:"header",description:o.description,required:!1!==o.required,schema:o.schema??{type:o.type??"string"},example:o.example})}})),e.request?.body&&Object.keys(e.request.body).length>0&&!a.requestBody){const t=e.request.body;a.requestBody={description:t.description,required:t.required,content:{"application/json":{schema:t.schema||{type:"object"},example:t.example}}}}e.response?.body&&(a.responses||(a.responses={}),a.responses[200]||(a.responses[200]={description:"Success",content:{"application/json":{schema:{type:"object",example:e.response.body}}}}))})})}tagSpecByPrefix(e,t={}){const{level:o=2,onlyIfEmpty:r=!0}=t,s=new Set(["get","post","put","delete","patch","head","options","trace"]),a=new Set((e.tags||[]).map(e=>e.name)),n=e.paths||{};for(const[e,t]of Object.entries(n)){if(!t)continue;const n="/"+e.split("/").filter(Boolean).slice(0,o).join("/");for(const[e,o]of Object.entries(t)){if(!s.has(e))continue;if(!o||"object"!=typeof o)continue;const t=o,c=Array.isArray(t.tags)&&t.tags.length>0;r&&c||(t.tags=[n]),a.add(n)}}return e.tags=Array.from(a).map(e=>({name:e})),e}generateSpec(){return{openapi:"3.0.0",info:{title:"API Documentation",version:"1.0.0",description:"Auto generated by koa-ts-core"},paths:this.paths,components:{schemas:{}}}}}const ee=(e,t,o,r,s)=>{if(!r||0===r.size)return;const{pathPrefix:a,moduleName:n,name:c}=B(t,"controller"),i=`${a}/${n}`,p=j(`${e}${a}/${c}`),d=e=>async(t,o)=>{const r=e(t);r instanceof Promise&&await r,await o()};r.forEach(e=>{const{method:t,path:r,handler:n,functionName:c,authRequired:u,middlewares:l}=e,g=[...l],h=Reflect.getMetadata(U,o.prototype,c);h?g.push(d(h)):p&&"function"==typeof p[c]&&g.push(d(p[c].bind(p))),g.push(((e,t)=>async(o,r)=>{let a=s(e,o);return a instanceof Promise&&(a=await a),a?t(o,r):o.throw(403,"无权限")})(u,n));const m=te(r,i,c,g,t);Y.getInstance().addRoute(o,c,t,m),e.path=m,e.apiPrePath=a})},te=(e,t,o,r,s)=>{const a=`${t}${e}`;if(o===k){const e=`${t}/index`;V[s](e,...r)}return V[s](a,...r),a};let oe=null;const re=()=>(oe||(oe=l.configure({appenders:{console:{type:"console"},file:{type:"dateFile",filename:"logs/app.log",pattern:".yyyy-MM-dd",compress:!0,numBackups:7,alwaysIncludePattern:!0}},categories:{default:{appenders:["console","file"],level:"info"}}})),oe),se=(e,t)=>S.run(e,()=>t()),ae=e=>{const{generator:t,exposeHeader:o=!0,headerName:r="x-request-id"}=e;return!1===t?async(e,t)=>{await t()}:async(e,s)=>{const a=await t(e);a&&(e.trackId=a,o&&e.set(r,a)),await s()}},ne=async(e,t)=>{const o=Date.now(),r=re().getLogger(),s=e.runtimeLog;s&&r.info("[REQUEST] %s %s %s - ip: %s - ua: %s",e.trackId,e.method,e.url,e.ip,e.get("user-agent")||"-");try{e.hook?.(e,"request"),await t(),e.hook?.(e,"response")}catch(t){const a=Date.now()-o;throw s&&r.error("[ERROR] %s %s %s - status: %s - cost: %dms - message: %s - stack: %s",e.trackId,e.method,e.url,t.status||500,a,t.message||"unknown error",t.stack||""),t}const a=Date.now()-o;s&&r.info("[RESPONSE] %s %s %s - status: %d - cost: %dms - length: %s",e.trackId,e.method,e.url,e.status,a,e.length||e.response.length||"-")},ce=async(e,t)=>{e.extra={get:e.query,post:e.request.body??{}},await t()};(()=>{const e=process.env.NODE_ENV||"production";let t;const o=process.env.ENV_DIR;if(o){if(t=c(o)?o:s(process.cwd(),o),!H(t))throw new Error(`[koa-ts-core] ENV_DIR is set but directory not found: ${t}`)}else{const e=A();let o=s(e,"env");if(H(o)||(o=s(e,"..","env")),!H(o))return void console.warn(`[koa-ts-core] env directory not found: tried ${s(e,"env")} and ${s(e,"..","env")}`);t=o}console.log("[koa-ts-core] load env path:",t),u.config({path:s(t,".env.local")});const r={development:".development.env",test:".test.env",production:".production.env"}[e];r&&u.config({path:s(t,`${r}.local`)}),r&&u.config({path:s(t,r)}),u.config({path:s(t,".common.env")})})(),global.isDev="development"===process.env.NODE_ENV;const ie=e=>{const{koaInstance:t,auth:o,error:r,log:s,hooks:a,phaseMiddlewares:n,koa2Cors:c,trackId:i,swagger:d,bodyParser:u}=e;return{app:t??new p,auth:{handler:o?.handler},error:{handler:r?.handler,exposeStack:r?.exposeStack??"production"!==process.env.NODE_ENV},log:{log4:s?.log4??!1,runtimeLog:s?.runtimeLog??!0},hooks:{register:a?.register},phaseMiddlewares:n??new Map,koa2Cors:c,trackId:{generator:!1!==i?.generator&&(async e=>{const t=i?.headerName??"x-request-id",o=e.get(t);return o||m()}),exposeHeader:i?.exposeHeader??!0,headerName:i?.headerName??"x-request-id"},swagger:d,bodyParser:u}},pe=(e,t)=>{const{log:o,hooks:r}=t;e.context.log4=(e=>{if(!1!==e)return"boolean"==typeof e&&!0===e?re():e(l)})(o.log4),e.context.runtimeLog=o.runtimeLog,e.context.hook=r.register,e.context.validated={params:{},get:{},post:{}}},de=e=>{(e=>{const{controllerModule:t,validateModule:o}=_();console.log("load controller path:",t),D(t,t=>{if(!t.name.endsWith(".ts")&&!t.name.endsWith(".js"))return;const r=j(t.wholePath);if(!r)return;const s=Reflect.getMetadata(v,r);s instanceof Map&&0!==s.size&&ee(o,t,r,s,(o,r)=>{if(!o)return!0;if(!e)throw new Error(`Route requires auth but no authCheckCallback provided. File: ${t.wholePath}`);return e({filePath:t,ctx:r})})})})(e),V.get("/",async e=>{e.body={code:200,message:"hello koa-ts-cli https://www.npmjs.com/package/koa-ts-core"}})},ue=async e=>{const t=ie(e),{app:o}=t;if(pe(o,t),de(t.auth.handler),t.swagger?.enabled){Y.getInstance().loadDocMetadata();const e=Y.getInstance().generateSpec();t.swagger.title&&(e.info.title=t.swagger.title),t.swagger.description&&(e.info.description=t.swagger.description),t.swagger.version&&(e.info.version=t.swagger.version);const r=t.swagger.path||"/swagger-ui";console.log(JSON.stringify(Y.getInstance().tagSpecByPrefix(e,{level:2}))),o.use(f({routePrefix:r,swaggerOptions:{spec:Y.getInstance().tagSpecByPrefix(e,{level:2,onlyIfEmpty:!1})}})),console.log(`[koa-ts-core] Swagger UI available at ${r}`)}return((e,t,o)=>{const{errorConfig:r,koa2Cors:s,trackConfig:a,bodyParser:n}=o,c={[F.AsyncContext]:[se],[F.ErrorHandling]:[(i=r?.handler,async(e,t)=>{try{await t()}catch(t){if(e.hook?.(e,"error"),i)i(t,e);else{const e=t.statusCode||t.status||500,o=t.code??-1;C(e,{message:t.message,code:o,data:"production"!==process.env.NODE_ENV?t.stack:void 0})}}})],[F.Logging]:[ae(a),ne],[F.Security]:[...s?[s(h)]:[]],[F.BodyParsing]:[g(n),ce],[F.Auth]:[],[F.Routing]:[V.routes()]};var i;const p=[F.AsyncContext,F.ErrorHandling,F.Logging,F.Security,F.BodyParsing,F.Auth,F.Routing];for(const o of p){const r=t.get(o),s=c[o]??[];if(r?.before)for(const t of r.before)e.use(t);for(const t of s)e.use(t);if(r?.use)for(const t of r.use)e.use(t);if(r?.after)for(const t of r.after)e.use(t)}})(o,t.phaseMiddlewares,{errorConfig:t.error,koa2Cors:t.koa2Cors,trackConfig:t.trackId,bodyParser:t.bodyParser}),await z(o),[o,V]};export{R as AuthRouter,$ as BaseException,k as INDEX_ROUTE,F as MiddlewarePhase,v as ROUTE_METADATA_KEY,q as Router,S as contextStore,ue as createKoaApp,C as errorRsp,I as getCurrentContext,_ as getSrcModulePaths,M as successRsp,O as unSuccessRsp};
|
|
@@ -2,7 +2,7 @@ export interface IMetaData {
|
|
|
2
2
|
method: string;
|
|
3
3
|
description: string;
|
|
4
4
|
path: string;
|
|
5
|
-
request: Request
|
|
5
|
+
request: Partial<Request>;
|
|
6
6
|
response: Response;
|
|
7
7
|
}
|
|
8
8
|
interface Response {
|
|
@@ -23,7 +23,7 @@ export interface ApiBodyDef {
|
|
|
23
23
|
}
|
|
24
24
|
interface Request {
|
|
25
25
|
header: Record<string, ApiParamDef>;
|
|
26
|
-
body:
|
|
26
|
+
body: Record<string, ApiParamDef>;
|
|
27
27
|
query: Record<string, ApiParamDef>;
|
|
28
28
|
}
|
|
29
29
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koa-ts-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.esm.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"koa2-swagger-ui": "^5.12.0",
|
|
27
27
|
"log4js": "^6.9.1",
|
|
28
28
|
"portfinder": "^1.0.38",
|
|
29
|
-
"pug": "^3.0.3",
|
|
30
29
|
"reflect-metadata": "^0.2.2"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
@@ -35,18 +34,19 @@
|
|
|
35
34
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
36
35
|
"@rollup/plugin-terser": "^0.4.4",
|
|
37
36
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
38
|
-
"koa": "^3.1.1",
|
|
39
37
|
"@types/koa": "^3.0.1",
|
|
40
38
|
"@types/koa-bodyparser": "^4.3.13",
|
|
41
|
-
"@types/koa__router": "^12.0.5",
|
|
42
39
|
"@types/koa2-cors": "^2.0.6",
|
|
40
|
+
"@types/koa__router": "^12.0.5",
|
|
43
41
|
"@types/node": "^25.0.3",
|
|
42
|
+
"koa": "^3.1.1",
|
|
43
|
+
"openapi-types": "^12.1.3",
|
|
44
44
|
"rollup": "^4.54.0",
|
|
45
45
|
"rollup-plugin-copy": "^3.5.0",
|
|
46
|
+
"rollup-plugin-copy-and-reference-dts": "0.0.2",
|
|
46
47
|
"rollup-plugin-delete": "^3.0.2",
|
|
47
48
|
"rollup-plugin-dts": "^6.3.0",
|
|
48
49
|
"rollup-plugin-dts-alias": "^0.0.4",
|
|
49
|
-
"rollup-plugin-copy-and-reference-dts": "0.0.2",
|
|
50
50
|
"rollup-plugin-pug": "^1.1.1",
|
|
51
51
|
"typescript": "^5.9.3",
|
|
52
52
|
"vitest": "^4.0.17"
|