ts5deco-express-controller
Version:
TypeScript 5 Modern Decorator Express Controller Framework
286 lines (268 loc) • 19.5 kB
JavaScript
;var Y=Object.create;var w=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,ee=Object.prototype.hasOwnProperty;var te=(e,t,s)=>t in e?w(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var se=(e,t)=>{for(var s in t)w(e,s,{get:t[s],enumerable:!0})},N=(e,t,s,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of X(t))!ee.call(e,r)&&r!==s&&w(e,r,{get:()=>t[r],enumerable:!(a=Q(t,r))||a.enumerable});return e};var D=(e,t,s)=>(s=e!=null?Y(Z(e)):{},N(t||!e||!e.__esModule?w(s,"default",{value:e,enumerable:!0}):s,e)),ae=e=>N(w({},"__esModule",{value:!0}),e);var C=(e,t,s)=>te(e,typeof t!="symbol"?t+"":t,s);var qe={};se(qe,{All:()=>ue,Authenticated:()=>le,Authorized:()=>me,BaseResponse:()=>i,Controller:()=>ne,Delete:()=>pe,FileResponse:()=>R,FileResponses:()=>V,Get:()=>oe,Head:()=>ce,JsonResponse:()=>g,METADATA_KEYS:()=>E,NoContentResponse:()=>F,Options:()=>he,Patch:()=>Te,Post:()=>ie,Put:()=>de,RedirectResponse:()=>h,RedirectResponses:()=>I,ResponseFactory:()=>c,TextResponse:()=>U,TypedApiResponseFactory:()=>$,TypedDelete:()=>we,TypedGet:()=>ge,TypedPost:()=>Re,TypedPut:()=>Me,Use:()=>A,Validated:()=>xe,addRouteMetadata:()=>y,convertExpressPath:()=>je,convertExpressPathToOpenApiPath:()=>Pe,convertOpenAPIPath:()=>Ve,convertOpenApiPathToExpressPath:()=>k,createApiResponse:()=>O,createPathMatcher:()=>L,createResponseFor:()=>Se,createRouter:()=>z,createTypedRoutes:()=>Ee,extractPathParameterNames:()=>G,extractPathParameterValues:()=>ye,generateTypes:()=>Ae,getControllerMetadata:()=>q,getRouteMetadata:()=>f,initProject:()=>Ie,isBaseResponse:()=>be,isValidOpenApiPath:()=>fe,registerController:()=>K,registerControllers:()=>ve,setControllerMetadata:()=>j,setRouteMetadata:()=>b});module.exports=ae(qe);var v=new WeakMap;function re(e){return v.has(e)||v.set(e,new Map),v.get(e)}function B(e,t,s){re(s).set(e,t)}function J(e,t){return v.get(t)?.get(e)}var E={CONTROLLER:Symbol("controller"),ROUTES:Symbol("routes"),PARAMETERS:Symbol("parameters"),MIDDLEWARES:Symbol("middlewares")};function j(e,t){B(E.CONTROLLER,t,e)}function q(e){return J(E.CONTROLLER,e)}function b(e,t){B(E.ROUTES,t,e)}function f(e){return J(E.ROUTES,e)||[]}function y(e,t){let s=f(e);b(e,[...s,t])}function ne(e){return function(t,s){let a="",r=[];return typeof e=="string"?a=e:e&&(a=e.path||"",r=e.middlewares||[]),a=("/"+a).replace(/\/+/g,"/").replace(/\/$/,"")||"/",j(t,{path:a,middlewares:r}),t}}function x(e){return function(t){return function(s,a){let r="",n=[];typeof t=="string"?r=t:t&&(r=t.path||"",n=t.middlewares||[]),r=("/"+r).replace(/\/+/g,"/").replace(/\/$/,"")||"/",a.addInitializer(function(){y(this.constructor,{path:r,method:e,middlewares:n,propertyKey:a.name})})}}}var oe=x("GET"),ie=x("POST"),de=x("PUT"),pe=x("DELETE"),Te=x("PATCH"),ce=x("HEAD"),he=x("OPTIONS");function ue(e){return function(t,s){let a=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"],r="",n=[];typeof e=="string"?r=e:e&&(r=e.path||"",n=e.middlewares||[]),r=("/"+r).replace(/\/+/g,"/").replace(/\/$/,"")||"/",s.addInitializer(function(){a.forEach(o=>{y(this.constructor,{path:r,method:o,middlewares:n,propertyKey:s.name})})})}}function A(...e){return function(t,s){s.addInitializer(function(){let r=f(this.constructor).map(n=>n.propertyKey===s.name?{...n,middlewares:[...e,...n.middlewares]}:n);r.length>0&&b(this.constructor,r)})}}function le(e){return A(e)}function me(e){return A(e)}function xe(e){return A(e)}function k(e){return e.replace(/{([^}]+)}/g,":$1")}function Pe(e){return e.replace(/:([^/]+)/g,"{$1}")}function G(e){let t=e.match(/{([^}]+)}/g);return t?t.map(s=>s.slice(1,-1)):[]}function fe(e){let t=(e.match(/{/g)||[]).length,s=(e.match(/}/g)||[]).length;if(t!==s)return!1;let a=0;for(let r of e)if(r==="{"){if(a++,a>1)return!1}else if(r==="}"&&(a--,a<0))return!1;return a===0}function L(e){let s=k(e).replace(/:[^/]+/g,"([^/]+)").replace(/\//g,"\\/");return new RegExp(`^${s}$`)}function ye(e,t){let s=G(e),a=L(e),r=t.match(a);if(!r||r.length!==s.length+1)return{};let n={};return s.forEach((o,d)=>{let T=r[d+1];T!==void 0&&(n[o]=T)}),n}var i=class{constructor(t){this.statusCode=t}};var g=class e extends i{constructor(s,a){super(s);this.data=a}send(s){s.status(this.statusCode).json(this.data)}static ok(s){return new e(200,s)}static created(s){return new e(201,s)}static badRequest(s){return new e(400,s)}static unauthorized(s){return new e(401,s)}static forbidden(s){return new e(403,s)}static notFound(s){return new e(404,s)}static internalError(s){return new e(500,s)}};var c=class{static create(t,s){let a=new g(t,s);return a.__brand=`api:${String(t)}:${String(t)}:${t}`,a.__statusCode=t,a.__data=s,a}static ok(t){return this.create(200,t)}static created(t){return this.create(201,t)}static badRequest(t){return this.create(400,t)}static notFound(t){return this.create(404,t)}static internalError(t){return this.create(500,t)}};function O(){return new $}var $=class{constructor(){C(this,"_allowedStatuses")}ok(t){return c.ok(t)}created(t){return c.created(t)}badRequest(t){return c.badRequest(t)}notFound(t){return c.notFound(t)}internalError(t){return c.internalError(t)}};function p(e){return function(t,s={}){return function(a,r){let{middlewares:n=[]}=s,o=t,d={},M=("/"+k(t)).replace(/\/+/g,"/").replace(/\/$/,"")||"/";r.addInitializer(function(){y(this.constructor,{path:M,method:e,middlewares:n,propertyKey:r.name})})}}}function ge(){return p("GET")}function Re(){return p("POST")}function Me(){return p("PUT")}function we(){return p("DELETE")}function Ee(){return{Get:p("GET"),Post:p("POST"),Put:p("PUT"),Delete:p("DELETE"),Patch:p("PATCH"),Head:p("HEAD"),Options:p("OPTIONS")}}function Se(){return O()}var H=require("express");function z(e){let t=(0,H.Router)();return e.forEach(s=>{K(t,s)}),t}function K(e,t){let s=q(t),a=f(t);if(!s){console.warn(`Controller ${t.name} does not have @Controller decorator`);return}let r=new t;a.forEach(n=>{let o=Ce(s.path,n.path),d=n.method.toLowerCase(),T=De(r,n.propertyKey),M=[...s.middlewares,...n.middlewares,T];typeof e[d]=="function"&&e[d](o,...M)})}function De(e,t){return async(s,a,r)=>{try{let n=await e[t](s,a,r);if(a.headersSent)return;if(n instanceof i){n.send(a);return}n!==void 0&&a.json(n)}catch(n){r(n)}}}function Ce(e,t){let s=`${e}${t}`.replace(/\/+/g,"/");return s==="/"?"/":s.replace(/\/$/,"")}function ve(e,t,s=""){let a=z(t);e.use(s,a)}var U=class e extends i{constructor(s,a){super(s);this.text=a}send(s){s.status(this.statusCode).send(this.text||"")}static ok(s){return new e(200,s||"OK")}static created(s){return new e(201,s||"Created")}static badRequest(s){return new e(400,s||"Bad Request")}static unauthorized(s){return new e(401,s||"Unauthorized")}static forbidden(s){return new e(403,s||"Forbidden")}static notFound(s){return new e(404,s||"Not Found")}static internalError(s){return new e(500,s||"Internal Server Error")}};var F=class extends i{constructor(){super(204)}send(t){t.status(204).end()}};var h=class extends i{constructor(s,a=!1){super(typeof a=="number"?a:a?301:302);C(this,"url");this.url=s}send(s){s.status(this.statusCode).redirect(this.url)}},I=class{static temporary(t){return new h(t,302)}static permanent(t){return new h(t,301)}static temporaryPreserveMethod(t){return new h(t,307)}static permanentPreserveMethod(t){return new h(t,308)}};var R=class extends i{constructor(s,a,r=!1){super(200);this.filePath=s;this.filename=a;this.asAttachment=r}send(s){this.asAttachment?this.filename?s.status(this.statusCode).download(this.filePath,this.filename):s.status(this.statusCode).download(this.filePath):this.filename?s.status(this.statusCode).sendFile(this.filePath,{headers:{"Content-Disposition":`inline; filename="${this.filename}"`}}):s.status(this.statusCode).sendFile(this.filePath)}},V=class{static inline(t,s){return new R(t,s,!1)}static attachment(t,s){return new R(t,s,!0)}};function be(e){return e instanceof i}var l=D(require("fs/promises")),u=D(require("path")),_=require("child_process");async function Ae(e){let{input:t,outputDir:s,apiTypesPath:a,utilsPath:r,generateAliases:n=!0,generateUtils:o=!0,openapiTypescriptOptions:d=[]}=e;try{await l.default.access(t)}catch{throw new Error(`OpenAPI specification file not found: ${t}`)}await l.default.mkdir(s,{recursive:!0});let T=u.default.join(s,"api.d.ts");console.log(`\u{1F4C4} Processing ${t}...`);let M=["npx openapi-typescript",`"${t}"`,"-o",`"${T}"`,...d].join(" ");try{(0,_.execSync)(M,{stdio:"inherit"}),console.log(`\u2705 Generated types: ${T}`)}catch(S){throw new Error(`Failed to generate types: ${S instanceof Error?S.message:String(S)}`)}let W=await ke(T);o&&r&&(await $e(r,T),console.log(`\u2705 Generated utility types: ${r}`)),n&&a&&(await Ue(a,W,r),console.log(`\u2705 Generated API type aliases: ${a}`))}async function ke(e){try{let s=(await l.default.readFile(e,"utf8")).match(/schemas:\s*{([^}]+)}/s);if(!s||!s[1])return[];let a=s[1],r=[],n=/(\w+):\s*{/g,o;for(;(o=n.exec(a))!==null;)o[1]&&r.push(o[1]);return r}catch(t){return console.warn("Warning: Could not extract schema names:",t instanceof Error?t.message:String(t)),[]}}async function $e(e,t){let a=`/**
* OpenAPI \uD0C0\uC785 \uC720\uD2F8\uB9AC\uD2F0
* openapi-typescript\uB85C \uC0DD\uC131\uB41C \uD0C0\uC785\uB4E4\uC744 \uC27D\uAC8C \uC0AC\uC6A9\uD558\uAE30 \uC704\uD55C \uD5EC\uD37C\uB4E4
*/
import type { paths, components } from './${u.default.relative(u.default.dirname(e),t).replace(/\.d\.ts$/,"").replace(/\\/g,"/")}';
/**
* OpenAPI \uC2A4\uD0A4\uB9C8\uC5D0\uC11C \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
* @example
* \`\`\`typescript
* type User = ExtractSchema<'User'>;
* type ErrorResponse = ExtractSchema<'Error'>;
* \`\`\`
*/
export type ExtractSchema<T extends keyof components['schemas']> =
components['schemas'][T];
/**
* OpenAPI \uACBD\uB85C\uC5D0\uC11C \uC751\uB2F5 \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
* @example
* \`\`\`typescript
* type GetUserResponse = ExtractResponse<'/users/{id}', 'get'>;
* type CreateUserResponse = ExtractResponse<'/users', 'post', 201>;
* \`\`\`
*/
export type ExtractResponse<
TPath extends keyof paths,
TMethod extends keyof paths[TPath],
TStatus extends number = 200
> = TPath extends keyof paths
? TMethod extends keyof paths[TPath]
? paths[TPath][TMethod] extends { responses: any }
? TStatus extends keyof paths[TPath][TMethod]['responses']
? paths[TPath][TMethod]['responses'][TStatus] extends {
content: { 'application/json': infer T }
}
? T
: never
: never
: never
: never
: never;
/**
* OpenAPI \uACBD\uB85C\uC5D0\uC11C \uC694\uCCAD \uBCF8\uBB38 \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
*/
export type ExtractRequestBody<
TPath extends keyof paths,
TMethod extends keyof paths[TPath]
> = TPath extends keyof paths
? TMethod extends keyof paths[TPath]
? paths[TPath][TMethod] extends {
requestBody: { content: { 'application/json': infer T } }
}
? T
: never
: never
: never;
/**
* OpenAPI \uACBD\uB85C\uC5D0\uC11C \uD30C\uB77C\uBBF8\uD130 \uD0C0\uC785\uC744 \uCD94\uCD9C\uD558\uB294 \uD5EC\uD37C \uD0C0\uC785
*/
export type ExtractParameters<
TPath extends keyof paths,
TMethod extends keyof paths[TPath]
> = TPath extends keyof paths
? TMethod extends keyof paths[TPath]
? paths[TPath][TMethod] extends { parameters: infer P }
? P
: never
: never
: never;
// \uC6D0\uBCF8 \uD0C0\uC785\uB4E4\uB3C4 re-export
export type { paths, components };
`;await l.default.mkdir(u.default.dirname(e),{recursive:!0}),await l.default.writeFile(e,a)}async function Ue(e,t,s){let r=`/**
* API \uD0C0\uC785 \uBCC4\uCE6D
* OpenAPI \uC2A4\uD399\uC5D0\uC11C \uC0DD\uC131\uB41C \uD0C0\uC785\uB4E4\uC744 \uC27D\uAC8C \uC0AC\uC6A9\uD558\uAE30 \uC704\uD55C \uBCC4\uCE6D \uC815\uC758
*
* \uC774 \uD30C\uC77C\uC740 \uC790\uB3D9 \uC0DD\uC131\uB429\uB2C8\uB2E4. \uD544\uC694\uC5D0 \uB530\uB77C \uC218\uC815\uD558\uC5EC \uC0AC\uC6A9\uD558\uC138\uC694.
*/
${s?`import type { ExtractSchema } from './${u.default.relative(u.default.dirname(e),s).replace(/\.ts$/,"").replace(/\\/g,"/")}';`:""}
/**
* \uC77C\uBC18\uC801\uC73C\uB85C \uC0AC\uC6A9\uB418\uB294 \uD0C0\uC785 \uBCC4\uCE6D\uB4E4
* \uD544\uC694\uC5D0 \uB530\uB77C \uCD94\uAC00\uD558\uAC70\uB098 \uC218\uC815\uD558\uC138\uC694
*/
${t.map(n=>{let o=Fe(n);return o?`export type ${o} = ExtractSchema<'${n}'>;`:`// export type ${n} = ExtractSchema<'${n}'>;`}).join(`
`)}
/**
* \uBAA8\uB4E0 \uC2A4\uD0A4\uB9C8 \uD0C0\uC785\uB4E4 (\uCC38\uACE0\uC6A9)
* \uC544\uB798 \uD0C0\uC785\uB4E4\uC744 \uC704\uC640 \uAC19\uC774 \uBCC4\uCE6D\uC73C\uB85C \uB9CC\uB4E4\uC5B4 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4:
*
${t.map(n=>` * - ${n}`).join(`
`)}
*/
`;await l.default.mkdir(u.default.dirname(e),{recursive:!0}),await l.default.writeFile(e,r)}function Fe(e){return{User:"User",Error:"ErrorResponse",CreateUserRequest:"CreateUserRequest",UpdateUserRequest:"UpdateUserRequest",PaginatedUserResponse:"PaginatedResponse",ApiError:"ApiError",ValidationError:"ValidationError"}[e]||null}var P=D(require("fs/promises")),m=D(require("path"));async function Ie(e="."){let t=m.default.resolve(e),s=m.default.join(t,"api");await P.default.mkdir(s,{recursive:!0}),await P.default.writeFile(m.default.join(s,"openapi.yaml"),`openapi: 3.0.0
info:
title: My API
version: 1.0.0
description: API for my application
servers:
- url: http://localhost:3000/api
description: Local development server
paths:
/users:
get:
summary: Get all users
operationId: getUsers
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 10
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedUserResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
summary: Create a new user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{id}:
get:
summary: Get a user by ID
operationId: getUserById
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
User:
type: object
required:
- id
- name
- email
properties:
id:
type: string
example: "123"
name:
type: string
example: "John Doe"
email:
type: string
format: email
example: "john@example.com"
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CreateUserRequest:
type: object
required:
- name
- email
- password
properties:
name:
type: string
example: "John Doe"
email:
type: string
format: email
example: "john@example.com"
password:
type: string
format: password
minLength: 8
Error:
type: object
required:
- error
- message
properties:
error:
type: string
example: "NOT_FOUND"
message:
type: string
example: "The requested resource was not found"
code:
type: string
example: "USER_NOT_FOUND"
statusCode:
type: integer
example: 404
PaginatedUserResponse:
type: object
required:
- data
- total
- page
- limit
- hasNext
- hasPrev
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
total:
type: integer
example: 100
page:
type: integer
example: 1
limit:
type: integer
example: 10
hasNext:
type: boolean
example: true
hasPrev:
type: boolean
example: false
`);let r=m.default.join(t,"package.json"),n=!1;try{let o=JSON.parse(await P.default.readFile(r,"utf8"));o.scripts||(o.scripts={}),o.scripts["generate:types"]||(o.scripts["generate:types"]="ts5deco-express-controller generate",n=!0),o.scripts.generate||(o.scripts.generate="npm run generate:types",n=!0),n&&(await P.default.writeFile(r,JSON.stringify(o,null,2)),console.log("\u2705 Updated package.json with recommended scripts"))}catch{console.log("\u{1F4A1} Recommended package.json scripts:"),console.log(' "generate:types": "ts5deco-express-controller generate"'),console.log(' "generate": "npm run generate:types"')}try{let o=m.default.join(t,".gitignore"),d="";try{d=await P.default.readFile(o,"utf8")}catch{}d.includes("src/types/generated/")||(d+=`
# Generated OpenAPI types
src/types/generated/
`,await P.default.writeFile(o,d),console.log("\u2705 Updated .gitignore to exclude generated types"))}catch{console.log("\u{1F4A1} Recommended .gitignore addition:"),console.log(" src/types/generated/")}console.log(`
\u{1F4C1} Created files:`),console.log(` ${m.default.relative(process.cwd(),m.default.join(s,"openapi.yaml"))}`)}function Ve(e){return e.replace(/{([^}]+)}/g,":$1")}function je(e){return e.replace(/:([^/]+)/g,"{$1}")}0&&(module.exports={All,Authenticated,Authorized,BaseResponse,Controller,Delete,FileResponse,FileResponses,Get,Head,JsonResponse,METADATA_KEYS,NoContentResponse,Options,Patch,Post,Put,RedirectResponse,RedirectResponses,ResponseFactory,TextResponse,TypedApiResponseFactory,TypedDelete,TypedGet,TypedPost,TypedPut,Use,Validated,addRouteMetadata,convertExpressPath,convertExpressPathToOpenApiPath,convertOpenAPIPath,convertOpenApiPathToExpressPath,createApiResponse,createPathMatcher,createResponseFor,createRouter,createTypedRoutes,extractPathParameterNames,extractPathParameterValues,generateTypes,getControllerMetadata,getRouteMetadata,initProject,isBaseResponse,isValidOpenApiPath,registerController,registerControllers,setControllerMetadata,setRouteMetadata});
//# sourceMappingURL=index.js.map