UNPKG

ts5deco-express-controller

Version:

TypeScript 5 Modern Decorator Express Controller Framework

286 lines (268 loc) 17.8 kB
var B=Object.defineProperty;var J=(e,t,s)=>t in e?B(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var E=(e,t,s)=>J(e,typeof t!="symbol"?t+"":t,s);var S=new WeakMap;function G(e){return S.has(e)||S.set(e,new Map),S.get(e)}function I(e,t,s){G(s).set(e,t)}function V(e,t){return S.get(t)?.get(e)}var D={CONTROLLER:Symbol("controller"),ROUTES:Symbol("routes"),PARAMETERS:Symbol("parameters"),MIDDLEWARES:Symbol("middlewares")};function j(e,t){I(D.CONTROLLER,t,e)}function q(e){return V(D.CONTROLLER,e)}function C(e,t){I(D.ROUTES,t,e)}function y(e){return V(D.ROUTES,e)||[]}function g(e,t){let s=y(e);C(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 c(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(){g(this.constructor,{path:r,method:e,middlewares:n,propertyKey:a.name})})}}}var de=c("GET"),pe=c("POST"),Te=c("PUT"),ce=c("DELETE"),he=c("PATCH"),ue=c("HEAD"),le=c("OPTIONS");function me(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=>{g(this.constructor,{path:r,method:o,middlewares:n,propertyKey:s.name})})})}}function v(...e){return function(t,s){s.addInitializer(function(){let r=y(this.constructor).map(n=>n.propertyKey===s.name?{...n,middlewares:[...e,...n.middlewares]}:n);r.length>0&&C(this.constructor,r)})}}function ge(e){return v(e)}function Re(e){return v(e)}function Me(e){return v(e)}function b(e){return e.replace(/{([^}]+)}/g,":$1")}function Ee(e){return e.replace(/:([^/]+)/g,"{$1}")}function L(e){let t=e.match(/{([^}]+)}/g);return t?t.map(s=>s.slice(1,-1)):[]}function Se(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 H(e){let s=b(e).replace(/:[^/]+/g,"([^/]+)").replace(/\//g,"\\/");return new RegExp(`^${s}$`)}function De(e,t){let s=L(e),a=H(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 R=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 h=class{static create(t,s){let a=new R(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 A}var A=class{constructor(){E(this,"_allowedStatuses")}ok(t){return h.ok(t)}created(t){return h.created(t)}badRequest(t){return h.badRequest(t)}notFound(t){return h.notFound(t)}internalError(t){return h.internalError(t)}};function p(e){return function(t,s={}){return function(a,r){let{middlewares:n=[]}=s,o=t,d={},f=("/"+b(t)).replace(/\/+/g,"/").replace(/\/$/,"")||"/";r.addInitializer(function(){g(this.constructor,{path:f,method:e,middlewares:n,propertyKey:r.name})})}}}function je(){return p("GET")}function qe(){return p("POST")}function Oe(){return p("PUT")}function Ne(){return p("DELETE")}function Be(){return{Get:p("GET"),Post:p("POST"),Put:p("PUT"),Delete:p("DELETE"),Patch:p("PATCH"),Head:p("HEAD"),Options:p("OPTIONS")}}function Je(){return O()}import{Router as z}from"express";function K(e){let t=z();return e.forEach(s=>{_(t,s)}),t}function _(e,t){let s=q(t),a=y(t);if(!s){console.warn(`Controller ${t.name} does not have @Controller decorator`);return}let r=new t;a.forEach(n=>{let o=Y(s.path,n.path),d=n.method.toLowerCase(),T=W(r,n.propertyKey),f=[...s.middlewares,...n.middlewares,T];typeof e[d]=="function"&&e[d](o,...f)})}function W(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 Y(e,t){let s=`${e}${t}`.replace(/\/+/g,"/");return s==="/"?"/":s.replace(/\/$/,"")}function Ke(e,t,s=""){let a=K(t);e.use(s,a)}var k=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 $=class extends i{constructor(){super(204)}send(t){t.status(204).end()}};var u=class extends i{constructor(s,a=!1){super(typeof a=="number"?a:a?301:302);E(this,"url");this.url=s}send(s){s.status(this.statusCode).redirect(this.url)}},U=class{static temporary(t){return new u(t,302)}static permanent(t){return new u(t,301)}static temporaryPreserveMethod(t){return new u(t,307)}static permanentPreserveMethod(t){return new u(t,308)}};var M=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)}},F=class{static inline(t,s){return new M(t,s,!1)}static attachment(t,s){return new M(t,s,!0)}};function nt(e){return e instanceof i}import m from"fs/promises";import l from"path";import{execSync as Q}from"child_process";async function xt(e){let{input:t,outputDir:s,apiTypesPath:a,utilsPath:r,generateAliases:n=!0,generateUtils:o=!0,openapiTypescriptOptions:d=[]}=e;try{await m.access(t)}catch{throw new Error(`OpenAPI specification file not found: ${t}`)}await m.mkdir(s,{recursive:!0});let T=l.join(s,"api.d.ts");console.log(`\u{1F4C4} Processing ${t}...`);let f=["npx openapi-typescript",`"${t}"`,"-o",`"${T}"`,...d].join(" ");try{Q(f,{stdio:"inherit"}),console.log(`\u2705 Generated types: ${T}`)}catch(w){throw new Error(`Failed to generate types: ${w instanceof Error?w.message:String(w)}`)}let N=await X(T);o&&r&&(await Z(r,T),console.log(`\u2705 Generated utility types: ${r}`)),n&&a&&(await ee(a,N,r),console.log(`\u2705 Generated API type aliases: ${a}`))}async function X(e){try{let s=(await m.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 Z(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 './${l.relative(l.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 m.mkdir(l.dirname(e),{recursive:!0}),await m.writeFile(e,a)}async function ee(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 './${l.relative(l.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=te(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 m.mkdir(l.dirname(e),{recursive:!0}),await m.writeFile(e,r)}function te(e){return{User:"User",Error:"ErrorResponse",CreateUserRequest:"CreateUserRequest",UpdateUserRequest:"UpdateUserRequest",PaginatedUserResponse:"PaginatedResponse",ApiError:"ApiError",ValidationError:"ValidationError"}[e]||null}import P from"fs/promises";import x from"path";async function gt(e="."){let t=x.resolve(e),s=x.join(t,"api");await P.mkdir(s,{recursive:!0}),await P.writeFile(x.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=x.join(t,"package.json"),n=!1;try{let o=JSON.parse(await P.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.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=x.join(t,".gitignore"),d="";try{d=await P.readFile(o,"utf8")}catch{}d.includes("src/types/generated/")||(d+=` # Generated OpenAPI types src/types/generated/ `,await P.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(` ${x.relative(process.cwd(),x.join(s,"openapi.yaml"))}`)}function Mt(e){return e.replace(/{([^}]+)}/g,":$1")}function wt(e){return e.replace(/:([^/]+)/g,"{$1}")}export{me as All,ge as Authenticated,Re as Authorized,i as BaseResponse,ne as Controller,ce as Delete,M as FileResponse,F as FileResponses,de as Get,ue as Head,R as JsonResponse,D as METADATA_KEYS,$ as NoContentResponse,le as Options,he as Patch,pe as Post,Te as Put,u as RedirectResponse,U as RedirectResponses,h as ResponseFactory,k as TextResponse,A as TypedApiResponseFactory,Ne as TypedDelete,je as TypedGet,qe as TypedPost,Oe as TypedPut,v as Use,Me as Validated,g as addRouteMetadata,wt as convertExpressPath,Ee as convertExpressPathToOpenApiPath,Mt as convertOpenAPIPath,b as convertOpenApiPathToExpressPath,O as createApiResponse,H as createPathMatcher,Je as createResponseFor,K as createRouter,Be as createTypedRoutes,L as extractPathParameterNames,De as extractPathParameterValues,xt as generateTypes,q as getControllerMetadata,y as getRouteMetadata,gt as initProject,nt as isBaseResponse,Se as isValidOpenApiPath,_ as registerController,Ke as registerControllers,j as setControllerMetadata,C as setRouteMetadata}; //# sourceMappingURL=index.mjs.map