@spailybot/moleculer-auto-openapi
Version:
Generate openapi scheme for moleculer
4 lines (3 loc) • 34.2 kB
JavaScript
var te="unknown-action",ue={server:"x-moleculer-web-server"},he={optional:"x-fastest-optional",description:"x-fastest-description",summary:"x-fastest-summary",deprecated:"x-fastest-deprecated"},f={...ue,...he},T=(c=>(c.GET="get",c.PUT="put",c.POST="post",c.DELETE="delete",c.OPTIONS="options",c.HEAD="head",c.PATCH="patch",c.TRACE="trace",c))(T||{}),de=["3.1"],re="3.1",H=Object.values(T),B=i=>H.includes(i?.toLowerCase()),A="*",w="rest",q=["oneOf","allOf","anyOf"],G=["put","post","patch"],$e=Object.values(T).filter(i=>!G.includes(i)),_="application/json",V="file",ne=`{{summary}}
({{action}}){{autoAlias}}`,se="//unpkg.com/swagger-ui-dist",W=/^[a-zA-Z0-9._-]+$/,$={json:["application/json"],urlencoded:["application/x-www-form-urlencoded"],text:["text/plain"],multipart:["multipart/form-data"],stream:["application/octet-stream"]};import Me from"moleculer";var fe=(r=>(r.NEXT_CALL="next-call",r.REFRESH="refresh",r.TIMEOUT="timeout",r))(fe||{});var ge={type:"object",properties:{rows:{type:"array",items:{type:"object"}},totalCount:{type:"number"}}},Ae={type:"array",items:{type:"object"}},Oe={type:"object"},ye={type:"object",properties:{name:{examples:["InternalServerError"],type:"string",description:"The name of the error"},message:{examples:["Example"],type:"string",description:"an helping message"},code:{type:"number",description:"the status code of the error (can be different of the HTTP status code)"},type:{type:"string",description:"additional information for the error"},data:{type:"object"}},required:["name","message","code"]},Se={description:"Server errors: 500, 501, 400, 404 and etc...",content:{"application/json":{schema:{allOf:[{$ref:"#/components/schemas/Error"},{examples:[{name:"InternalServerError",message:"Internal Server Error",code:500}]}]}}}},be={description:"Need auth",content:{"application/json":{schema:{allOf:[{$ref:"#/components/schemas/Error"},{type:"object",examples:[{name:"UnAuthorizedError",message:"Unauthorized",code:401}]}]}}}},Pe={description:"Fields invalid",content:{"application/json":{schema:{allOf:[{$ref:"#/components/schemas/Error"},{type:"object",examples:[{name:"MoleculerClientError",message:"Error message",code:422,data:[{name:"fieldName",message:"Field invalid"},{name:"arrayField[0].fieldName",message:"Whats wrong"},{name:"object.fieldName",message:"Whats wrong"}]}]}]}}}},Ee={description:"",content:{"application/json":{schema:{oneOf:[{$ref:"#/components/schemas/DbMixinList"},{$ref:"#/components/schemas/DbMixinFindList"},{$ref:"#/components/schemas/Item"}]}}}},Re={description:"File not exist",content:{"application/json":{schema:{allOf:[{$ref:"#/components/schemas/Error"},{type:"object",examples:[{name:"MoleculerClientError",message:"File missing in the request",code:400}]}]}}}},Te={description:"File too big",content:{"application/json":{schema:{allOf:[{$ref:"#/components/schemas/Error"},{type:"object",examples:[{name:"PayloadTooLarge",message:"Payload too large",code:413,type:"PAYLOAD_TOO_LARGE",data:{fieldname:"file",filename:"4b2005c0b8.png",encoding:"7bit",mimetype:"image/png"}}]}]}}}},_e={DbMixinList:ge,DbMixinFindList:Ae,Item:Oe,Error:ye},xe={ServerError:Se,UnauthorizedError:be,ValidationError:Pe,ReturnedData:Ee,FileNotExist:Re,FileTooBig:Te},Y={schemas:_e,responses:xe};import ae from"path/posix";var ie=i=>i.fullName?i.fullName:i.version!=null&&i.settings?.$noVersionPrefix!==!0?(typeof i.version=="number"?"v"+i.version:i.version)+"."+i.name:i.name,oe=(i,t)=>{let e=[],r;for(;(r=i.exec(t))!==null;)r.index===i.lastIndex&&i.lastIndex++,e.push([...r.slice(1)]);return e},O=(i="")=>ae.resolve("/",ae.normalize(i)),pe=i=>i===A||i===w||B(i),ce=i=>i===A||B(i);function z(i){return i?(t,e)=>t[i]?.localeCompare(e[i],"en",{sensitivity:"base"})??-1:(t,e)=>t.localeCompare(e,"en",{sensitivity:"base"})}import Ie from"path/posix";var v=class{actionType;path;method;action;actionName;get fullPath(){return this.alias.fullPath}alias;constructor(t,e,r){this.alias=t,this.actionType=t.type,this.path=t.path,this.method=e,r&&this.setAction(r)}setAction(t){this.action=t}};import ve from"path/posix";var b=class{fullPath;get path(){return this._path}set path(t){this._path=O(t)}get method(){return this._method}set method(t){if(!ce(t))throw new Error(`"${t}" is not a valid method`);this._method=t.toLowerCase()}route;type;_method="*";_path="";action;actionSchema;service;openapi;skipped=!1;busboyConfig;constructor(t,e){this.route=e,this.type=t.type,this.busboyConfig=t.busboyConfig,this.method=t.method??A,this.path=t.path??"/",this.fullPath=ve.join(e?.path??"/",t.path??"/"),this.action=t.action,t.openapi===!1?this.skipped=!0:this.openapi=t.openapi}isJokerAlias(){return this.method===A}getMethods(){return(this.method===A?H:[this.method])??[]}toJSON(){return{method:this.method,type:this.type,path:this.path,action:this.action,openapi:this.openapi}}getPaths(){return this.getMethods().map(t=>new v(this,t,this.actionSchema))}};var D=class{constructor(t,e,r={},n=!0){this.logger=t;this.route=e;this.aliases=r;this.skipUnresolvedActions=n}getAliases(){return Object.entries(this.aliases??{}).flatMap(([t,e])=>{let r=this.extractAliasInformation(t,e),n=this.getSubAliases(r??{}).map(s=>{let a=new b(s,this.route);return a.skipped=!0,a});return r?r.action&&!r.action.match(W)?(this.logger.error(`alias "${t}" from route "${this.route.path}" can't be added ton openapi . because the name "${r.action}" need to match pattern ${W.toString()}`),n):this.getSubAliases(r).map(s=>new b(s,this.route)):(this.logger.warn(`alias "${t}" from route "${this.route.path}" is skipped`),n)})}extractAliasSubInformations(t){if((r=>!!r&&["action","handler"].some(n=>!!r[n]))(t))return t;if(Array.isArray(t)){let r=t.reduce((n,s)=>!s||typeof s!="string"?n:s,void 0);return!r&&this.skipUnresolvedActions?void 0:{action:r}}else return typeof t!="string"?this.skipUnresolvedActions?void 0:{action:void 0}:{action:t}}extractAliasInformation(t,e){let r=this.extractAliasSubInformations(e);if(!r)return;let n=t.split(/\s+/);if(n.length===1&&(r.path=r.path??n[0]),n.length>1&&(r.path=r.path??n[1],r.method=r.method??n[0]),!r.actionType&&r.action?.includes(":")){let[s,a]=r.action.split(":");r.type=s,r.action=a}if(r.method?r.method=r.method.toLowerCase():r.method=A,!pe(r.method)){this.logger.warn(`"${r.method}" is not a valid http method`);return}return r}getSubAliases(t){if(t.method!==w)return[t];let e=t.action,r={list:{method:"get",action:`${e}.list`,path:`${t.path}`},get:{method:"get",action:`${e}.get`,path:`${t.path}/:id`},create:{method:"post",action:`${e}.create`,path:`${t.path}`},update:{method:"put",action:`${e}.update`,path:`${t.path}/:id`},patch:{method:"patch",action:`${e}.patch`,path:`${t.path}/:id`},remove:{method:"delete",action:`${e}.remove`,path:`${t.path}/:id`}};return Object.entries(r).filter(([n])=>(t.only?t.only.includes(n):!0)&&(t.except?!t.except.includes(n):!0)).map(([,n])=>({...t,...n}))}};var I=class i{constructor(t,e,r,n,s=!0){this.logger=t;this.skipUnresolvedActions=s;this.path=i.formatPath(e?.path,r),this.bodyParsers=e.bodyParsers,this.busboyConfig=e.busboyConfig,this.autoAliases=e.autoAliases??!1,this.openapi=e.openapi,this.openApiService=n,this.apiService=r,this.aliases=new D(this.logger,this,e.aliases,this.skipUnresolvedActions).getAliases()}aliases;path;bodyParsers;autoAliases;openapi;openApiService;apiService;busboyConfig;static formatPath(t,e){return O(Ie.join(e?.settings?.path??"/",t??"/"))}searchAlias(t){return this.aliases.find(e=>(e.method.toLowerCase()===A||e.method?.toLowerCase()===t.methods?.toLowerCase())&&O(e.path?.toLowerCase())===O(t.path?.toLowerCase()))}};var C=class{constructor(t){this.logger=t;this.logger.debug("RoutesParser.constructor()")}async parse(t,e,r,n){this.logger.debug("RoutesParser.parse()");let s=new Map,a=new Map;n.forEach(p=>Object.values(p.actions??{}).forEach(l=>{typeof l=="boolean"||typeof l=="function"||!l.name||s.set(l.name,{service:p,action:l})}));let o=ie(e),c=e.settings?.routes||[];return Array.isArray(c)?(c.forEach(p=>{let l=p.name??p.path;if(this.logger.debug(`RoutesParser.parse() - check route ${l}`),p?.openapi===!1){this.logger.debug(`RoutesParser.parse() - skip route ${l} because openapi = false`);return}let u=new I(this.logger,p,e,t.service?.schema,r);a.set(`${o}-${u.path}`,u)}),(await this.fetchAliasesForService(t,o)??[]).filter((p,l,u)=>l===u.findIndex(S=>S.fullPath===p.fullPath&&S.methods===p.methods)).flatMap(p=>{this.logger.debug(`RoutesParser.parse() - checking alias ${p.path} for path ${p.fullPath}`);let l=a.get(`${o}-${O(p.routePath)}`);if(!l){this.logger.debug(`RoutesParser.parse() - alias ${p.fullPath} is skipped because not linked to a route (can be normal if route use openapi = false)`);return}let u=l?.searchAlias(p);if(!u){if(l&&!l.autoAliases){this.logger.error(`fail to get alias configuration for alias ${p.methods} "${p.fullPath}"`);return}this.logger.debug(`RoutesParser.parse() - alias ${p.fullPath} seems to use autoAliases`);let S=new b({path:p.path,method:p.methods,action:p.actionName??void 0,openapi:l?.openapi},l);return p.fullPath&&(S.fullPath=p.fullPath),S}if(u.skipped){this.logger.debug(`RoutesParser.parse() - skip alias ${u.fullPath} because openapi = false`);return}return u}).filter(Boolean).map(p=>{if(!p.action)return p;let l=s.get(p.action);if(!l)return this.logger.warn(`fail to get details about action "${p.action}"`),r?void 0:p;if(l.action.openapi!==!1)return p.actionSchema=l.action,p.service=l.service,p}).filter(Boolean)):(this.logger.debug(`RoutesParser.parse() - service ${o} seems to not be a moleculer-web services`),[])}fetchAliasesForService(t,e){return t.call(`${e}.listAliases`,{withActionSchema:!1,grouping:!1})}};var me=i=>{let t=typeof i;if(["boolean","object","number","string","integer","array"].includes(t))return t},d=i=>typeof i.default=="function"?void 0:i.default;var F=class{constructor(t,e){this.validator=t;this.additionalMappersFn=e;this.mappers=je(this.getMapperFn()),this.load()}mappers;getMapperFn(){return{getSchemaObjectFromSchema:(...t)=>this.getSchemaObjectFromSchema(...t),getSchemaObjectFromRule:(...t)=>this.getSchemaObjectFromRule(...t)}}_loadingPromise;async load(){if(this._loadingPromise)return this._loadingPromise;this._loadingPromise=new Promise(async(t,e)=>{try{let r=this.getMapperFn();Object.entries(await this.additionalMappersFn?.(r.getSchemaObjectFromRule,r.getSchemaObjectFromSchema)??{}).forEach(([n,s])=>{this.mappers[n]=s}),t()}catch(r){e(r)}}),await this._loadingPromise}getValidationRules(t){return Object.fromEntries(Object.entries(t).filter(([e])=>!e.startsWith("$$")))}getMetas(t){return Object.fromEntries(Object.entries(t).filter(([e])=>e.startsWith("$$")))}getSchemaObjectFromSchema(t){return Object.fromEntries(Object.entries(this.getValidationRules(t)).map(([e,r])=>[e,this.getSchemaObjectFromRule(r,void 0,t)]).filter(Boolean))}getSchemaObjectFromRootSchema(t){if(t.$$root!==!0)throw new Error("this function only support $$root objects");return delete t.$$root,this.getSchemaObjectFromRule(t)}getSchemaObjectFromRule(t,e,r){if(!this.validator||!this.mappers?.string)throw new Error(`bad initialisation . validator ? ${!!this.validator} | string mapper ${!!this.mappers?.string}`);let n=typeof t=="object"?Array.isArray(t)?[...t]:{...t}:t,s=Array.isArray(n)||typeof n!="object"||!n.$$oa?[]:[{property:"description",extension:f.description},{property:"summary",extension:f.summary},{property:"deprecated",extension:f.deprecated}].map(({property:h,extension:p})=>[p,n.$$oa?.[h]]),a=this.validator.getRuleFromSchema(n)?.schema,o={...e,...a},m=(this.mappers[o.type]||this.mappers.string)(o,r);if(m)return o.optional&&(m[f.optional]=!0),s.forEach(([h,p])=>{m[h]=p}),m}},je=({getSchemaObjectFromRule:i,getSchemaObjectFromSchema:t})=>({any:e=>{let r=d(e);return{default:r,examples:r?[r]:void 0}},array:e=>{let r=(e.items?i(e.items,{enum:e.enum}):void 0)??{},n=d(e),s={type:"array",examples:n?[n]:void 0,uniqueItems:e.unique,default:n,items:r};return e.length?(s.maxItems=e.length,s.minItems=e.length):(s.maxItems=e.max,s.minItems=e.min),s},boolean:e=>{let r=d(e);return{type:"boolean",default:r,examples:r!==void 0?[r]:[!0,!1]}},class:()=>{},currency:e=>{let r=d(e),n;if(e.customRegex)n=e.customRegex.toString();else{let s=e.currencySymbol??null,a=e.thousandSeparator??",",o=e.decimalSeparator??".",c=e.symbolOptional?"?":"",m=s?`\\${s}${c}`:"",h="(?=.*\\d)^(-?~1|~1-?)(([0-9]\\d{0,2}(~2\\d{3})*)|0)?(\\~3\\d{1,2})?$".replace(/~1/g,m).replace("~2",a).replace("~3",o);n=new RegExp(h).source}return{type:"string",pattern:n,default:r,examples:r?[r]:void 0,format:"currency"}},date:e=>{let r=d(e);if(!e.convert)return;let n=new Date(r?.toString()??""),s=isNaN(n.getTime())?new Date:n,a=[s.toISOString(),s.getTime()];return{type:"string",default:r,format:"date-time",examples:a}},email:e=>{let r=d(e),n=/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,s=/^\S+@\S+\.\S+$/,a=e.mode=="precise"?n:s;return{type:"string",format:"email",default:r,pattern:new RegExp(a).source,maxLength:e.max,minLength:e.min,examples:[r??"foo@bar.com"]}},enum:e=>i({type:"string",enum:e.values}),equal:(e,r)=>{let n=d(e);return e.field&&r?.[e.field]?i(r?.[e.field]):{type:e.strict?me(e.value):"string",default:n,examples:n?[n]:void 0,enum:e.value?[e.value]:void 0}},forbidden:()=>{},function:()=>{},luhn:e=>{let r=d(e);return{type:"string",default:r,pattern:"^(\\d{1,4} ){3}\\d{1,4}$",examples:r?[r]:void 0,format:"luhn"}},mac:e=>{let r=d(e),n=/^((([a-f0-9][a-f0-9]+-){5}|([a-f0-9][a-f0-9]+:){5})([a-f0-9][a-f0-9])$)|(^([a-f0-9][a-f0-9][a-f0-9][a-f0-9]+[.]){2}([a-f0-9][a-f0-9][a-f0-9][a-f0-9]))$/i;return{type:"string",default:r,pattern:new RegExp(n).source,examples:r?[r]:["01:C8:95:4B:65:FE","01C8.954B.65FE","01-C8-95-4B-65-FE"],format:"mac"}},multi:e=>{let r=d(e);return Array.isArray(e.rules)?{oneOf:e.rules.map(s=>i(s)).filter(Boolean),default:r,examples:r?[r]:void 0}:void 0},number:e=>{let r=d(e),n=r??e.enum?.[0]??e.min??e.max,s={type:"number",default:r,examples:n?[n]:void 0};return e.positive&&(s.minimum=0),e.negative&&(s.maximum=0),e.max&&(s.maximum=e.max),e.min&&(s.minimum=e.min),e.equal&&(s.maximum=e.equal,s.minimum=e.equal),s},object:e=>{let r=d(e),n=e.props??e.properties,s=n?t(n):void 0;return{type:"object",minProperties:e.minProps,maxProperties:e.maxProps,default:r,properties:s,examples:r?[r]:void 0}},record:e=>{let r=d(e),n=e.value?i(e.value):void 0;return{type:"object",default:r,additionalProperties:n}},string:e=>{let r=d(e),n={default:r,type:"string"};e.length?(n.maxLength=e.length,n.minLength=e.length):(n.maxLength=e.max,n.minLength=e.min);let s;e.pattern?n.pattern=new RegExp(e.pattern).source:e.contains?(n.pattern=`.*${e.contains}.*`,s=e.contains):e.numeric?(n.pattern="^[0-9]+$",n.format="numeric",s="12345"):e.alpha?(n.pattern="^[a-zA-Z]+$",n.format="alpha",s="abcdef"):e.alphanum?(n.pattern="^[a-zA-Z0-9]+$",n.format="alphanum",s="abc123"):e.alphadash?(n.pattern="^[a-zA-Z0-9_-]+$",n.format="alphadash",s="abc-123"):e.singleLine?(n.pattern="^[^\\r\\n]*$",n.format="single-line",s="abc 123"):e.hex?(n.pattern="^([0-9A-Fa-f]{2})+$",n.format="hex",s="48656c6c6f20576f726c64"):e.base64&&(n.pattern="^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",n.format="byte",s="aGVsbG8gd29ybGQ="),n.enum=e.enum;let a=r??e.enum?.[0]??s;return a&&(n.examples=[a]),n},tuple:e=>{let r=d(e),n=i({type:"array",default:r,length:2});return e.items&&(n.items={oneOf:e.items.map(s=>i(s)).filter(Boolean)}),r&&(n.examples=[r]),n},url:e=>{let r=d(e);return{type:"string",format:"url",default:r,examples:[r??"https://foobar.com"]}},uuid:e=>{let r=d(e),n;switch(e.version){case 0:n="00000000-0000-0000-0000-000000000000";break;case 1:n="45745c60-7b1a-11e8-9c9c-2d42b21b1a3e";break;case 2:n="9a7b330a-a736-21e5-af7f-feaf819cdc9f";break;case 3:n="9125a8dc-52ee-365b-a5aa-81b0b3681cf6";break;case 4:default:n="10ba038e-48da-487b-96e8-8d3b99b6d18a";break;case 5:n="fdda765f-fc57-5604-a269-52a7df8164ec";break;case 6:n="a9030619-8514-6970-e0f9-81b9ceb08a5f";break}return{type:"string",format:"uuid",default:r,examples:r?[r]:[n]}},objectID:e=>{let r=d(e),n="507f1f77bcf86cd799439011";return{type:"string",format:"ObjectId",default:r,minLength:n.length,maxLength:n.length,examples:r?[r]:[n]}},custom:()=>{}});var L=class{static generateResponses(t,e){let r=t.responses??{},n="200";if(t.response){let s={description:""};if(t.response.description===void 0)s.content={[e]:t.response};else{let a=t.response;s.description=a.description,s.headers=a.headers,s.links=a.links,n=a.statusCode?.toString()??n,a.content&&(s.content={[a.type??e]:a.content})}r[n]=s}return r}static mergeObjects(t,e={}){if(!e)throw new Error("need an input object to apply merge");return Object.entries(e).forEach(([r,n])=>{if(n===!1){delete t[r];return}t[r]=n}),t}static mergeCommons(t,e=[]){return e.reduce((r,n)=>{let s=r??{components:{}};return s?.components||(s.components={}),n&&Object.keys(n).forEach(a=>{let o=a;if(o==="components"){Object.keys(n.components??{}).forEach(c=>{let m=c;s.components||(s.components={}),s.components[m]||(s.components[m]={}),this.mergeObjects(s.components[m],n?.components?.[m])});return}if(o==="responses"){s?.[o]||(s[o]={}),this.mergeObjects(s[o],n?.[o]);return}if(o==="tags"){s.tags=(n.tags??[]).reduce((c=[],m)=>m===null?[]:typeof m!="string"?(m.name&&t.set(m.name,{...t.get(m.name)??{},...m}),c):(t.set(m,{...t.get(m)??{},name:m}),[...c,m]),s.tags);return}if(n[o]===!1&&typeof s[o]!="boolean"){delete s[o];return}s[o]=n[o]}),s},{})}static merge(t,e,r,n,s,a){return[r?.openapi,s?.settings?.addServiceNameToTags&&r?.service?.name?{tags:[r.service.name]}:void 0,r?.service?.settings?.openapi,n?.openapi].reduce((o,c)=>(c&&(c.responses=this.generateResponses(c,s?.settings?.defaultResponseContentType??_),o=this.mergeCommons(t,[o,c]),o.summary=c.summary??o.summary,o.security=c.security??o.security),o),this.mergeCommons(t,[s?.settings?.openapi,a?.settings?.openapi,e.openapi]))}};var U=class{constructor(t,e,r,n){this.logger=t;this.converter=new F(e,n),this.document=r}components={schemas:{},responses:{},parameters:{},examples:{},requestBodies:{},headers:{},securitySchemes:{},links:{},callbacks:{},pathItems:{}};document;converter;isLoaded;async load(){await this.converter.load(),this.isLoaded=!0}generate(t,e){this.isLoaded||this.logger.warn("generator : converter is not loaded, custom mapper can be not be enabled");let r=new Map;this.document.openapi&&(this.logger.warn("setting manually the openapi version is not supported"),delete this.document.openapi);let n={openapi:`${t}.0`,...this.document,servers:this.document.servers??[],tags:[],components:this.cleanComponents(this.document.components)};n.responses&&delete n.responses;let s=new Map;return e.sort(z("fullPath")),e.forEach(a=>{n.paths||(n.paths={});let o=a.route,{apiService:c,openApiService:m}=o,h=this.formatParamUrl(O(a.fullPath)),p=n.paths?.[h]??{};a.isJokerAlias()&&(p.description=a.actionSchema?.openapi?.description,p.summary=a.actionSchema?.openapi?.summary),a.getPaths().forEach(l=>{let u=l.method,S=`${h}.${u}`,y=p[u];if(y){if((y[f.server]||y.servers?.length)&&a.route.apiService.settings?.openapi?.server?.url&&!y.servers?.find(R=>R.url===a.route.apiService.settings?.openapi?.server?.url)){let R=a.route.apiService.settings.openapi.server;if(!y.servers?.length){y.servers=[];let M=y[f.server];M&&y.servers.push(M)}y.servers.push(R),this.addServerToDocument(n,R);return}let E=s.get(S);this.logger.warn(`${u.toUpperCase()} ${h} is already register by action ${E??"<unamedAction>"} skip`);return}s.set(S,l.action?.name);let g=L.merge(r,o,a,l.action,m,c),{parameters:Q,requestBody:le}=this.extractParameters(u,h,a)??{};g?.parameters&&Q.push(...g.parameters),this.components=this.mergeComponents(this.components,this.cleanComponents(g.components));let j={summary:a.isJokerAlias()?void 0:g?.summary,description:a.isJokerAlias()?void 0:g?.description,deprecated:g.deprecated,operationId:g?.operationId,externalDocs:g?.externalDocs,security:g?.security,tags:this.handleTags(n,r,g?.tags),parameters:Q,requestBody:le,responses:g?.responses};if(a.route.apiService.settings?.openapi?.server){let E=a.route.apiService.settings.openapi.server;j[f.server]=E,this.addServerToDocument(n,E)}let ee={summary:g?.summary??"",action:a.action??te,autoAlias:a.route.autoAliases?"[autoAlias]":""},x=a.route?.openApiService?.settings?.summaryTemplate;(typeof x=="string"||x===void 0)&&(j.summary=Object.entries(ee).reduce((E,[R,M])=>E.replace(new RegExp(`{{${R}}}`,"g"),M??""),x??ne).trim()),typeof x=="function"&&(j.summary=x(ee)),p[u]=j}),n.paths[h]=p}),n.tags?.sort(z("name")),n.components=this.mergeComponents(n.components,this.components),this.removeExtensions(n)}addServerToDocument(t,e){t.servers||(t.servers=[]),t.servers.some(r=>r.url===e.url)||t.servers.push(e)}mergeComponents(t,e){return Object.keys(e).reduce((r,n)=>Object.keys(e?.[n]).length?{...r,[n]:{...t[n],...e[n]}}:r,{...t})}addQueryParameters(t,e,r,n){if(e.openapi?.queryParameters)return e.openapi.queryParameters.map(a=>({...a,in:"query"}));let s=this.getParameters(r,n,!1);return Object.entries(s).forEach(([a,o])=>{let c=this.converter.getSchemaObjectFromRule(o);if(!c)return;let m=this.getComponent(c),h={name:a,in:"query",style:c.type==="object"?"deepObject":void 0,explode:c.type==="object"?!0:void 0,required:m[f.optional]!==!0||void 0,schema:c};if(!t.some(p=>p.name===a)){t.push(h);return}t=t.map(p=>p.name!==a?p:{...h,in:"path",required:!0})}),t}getRequestBody(t,e,r,n,s=[]){if(t.openapi?.requestBody)return t.openapi?.requestBody;if(!t.action)return;let a=n?.$$oa??{},o=this.getParameters(e,r,!0);if(Object.keys(o).length>0){let c={...n,...o},m=this.createRequestBodyFromParams(t.action,c,s),h=Object.entries(t.route?.bodyParsers||{}).filter(([,u])=>!!u).flatMap(([u])=>$[u]??[]),p=(h?.length?h:[t.route?.openApiService?.settings?.defaultResponseContentType])??[_],l=!1;if(this.isReferenceObject(m)){let u=this.getComponentByRef(m.$ref);if(!u)throw new Error(`fail to get schema from path ${m.$ref}`);l=(u.required??[]).length>0}return{description:a.description,summary:a.summary,required:l,content:Object.fromEntries(p.map(u=>[u,{schema:m}]))}}}extractParameters(t,e,r){let n=r?.actionSchema?.params??{},s=this.converter.getMetas(n),a=["multipart","stream"].includes(r.type??""),o=r.openapi?.pathParameters?r.openapi.pathParameters.map(p=>({...p,in:"path"})):this.extractParamsFromUrl(e),m={parameters:this.addQueryParameters(o,r,a?"get":t,n)},h=o.map(p=>p.name);return a?m.requestBody=r.openapi?.requestBody?r.openapi?.requestBody:this.generateFileUploadBody(r,h):m.requestBody=this.getRequestBody(r,t,n,s,h),m}getParameters(t,e,r){let n=G.includes(t);return Object.fromEntries(Object.entries(this.converter.getValidationRules(e)).map(([s,a])=>{let o=a?.$$oa?.in;if((o?o==="body":n)===r)return[s,a]}).filter(Boolean))}generateFileUploadBody(t,e){let r=t.type?$[t.type]:$.multipart,n={},s={type:"string",format:"binary"};if(t.type==="stream")n.type=s.type,n.format=s.format;else{if(t.actionSchema?.params?.$$root===!0)throw new Error("$$root parameters is not supported on multipart");let a=t.busboyConfig?.limits?.files??t?.route?.busboyConfig?.limits?.files,o=t.route.openApiService?.settings?.multiPartFileFieldName??V;n.allOf=[{type:"object",properties:{[o]:a===1?s:{type:"array",items:s,maxItems:a}},required:[o]}]}return{required:!0,content:{[r[0]]:{schema:n}}}}isReferenceObject(t){return!!t?.$ref}getComponent(t){if(!this.isReferenceObject(t))return t;let e=this.getComponentByRef(t.$ref);if(!e)throw new Error(`fail to get component "${t.$ref}`);return e}getComponentByRef(t){let e=t.split("/").filter(r=>r!=="");if(!(e.length<4||e[0]!=="#"||e[1]!=="components"||!Object.keys(this.components).includes(e[2])))return e.slice(2).reduce((r,n)=>r&&r.hasOwnProperty(n)?r[n]:void 0,this.components)}createRequestBodyFromParams(t,e,r=[],n={}){if(e.$$root===!0)return this.converter.getSchemaObjectFromRootSchema(e);let s=this.converter.getSchemaObjectFromSchema(e),a=Object.fromEntries(Object.entries(s).filter(([o,c])=>!r.includes(o)&&c));return this._createSchemaComponentFromObject(t,a,n)}extractParamsFromUrl(t=""){return[...oe(/{(\w+)}/g,t).flat()].map(e=>({name:e,in:"path",required:!0,schema:{type:"string"}}))}_createSchemaComponentFromObject(t,e,r={}){this.components.schemas||(this.components.schemas={});let n=[],s=Object.fromEntries(Object.entries(e).map(([a,o])=>{let c=`${t}.${a}`;return o[f.optional]!=!0&&n.push(a),[a,this._createSchemaPartFromRule(c,o)]}));return this.components.schemas[t]&&this.logger.warn(`Generator - schema ${t} already exist and will be overwrite`),this.components.schemas[t]={type:"object",properties:s,required:n.length>0?n:void 0,default:r.default},{$ref:`#/components/schemas/${t}`}}formatParamUrl(t=""){let e=t.indexOf("/:");if(e===-1)return t;let r=t.indexOf("/",++e);return r===-1?t.slice(0,e)+"{"+t.slice(++e)+"}":this.formatParamUrl(t.slice(0,e)+"{"+t.slice(++e,r)+"}"+t.slice(r))}_createSchemaPartFromRule(t,e){let r=this.extractSystemParams(e);if(e.description=r.description,e.title=r.summary,e.deprecated=r.deprecated,e.type=="object"&&e.properties)return{summary:e.title,deprecated:e.deprecated,description:e.description,...this._createSchemaComponentFromObject(t,e.properties,{default:e.default})};if(e.type==="array"&&e.items)return{...e,items:this._createSchemaPartFromRule(t,e.items)};if(q.some(n=>e[n])){let n=0;q.forEach(s=>{e[s]&&(e[s]=e[s].map(a=>{if(a.type!=="object")return a;let o=`${t}.${n++}`;return this._createSchemaPartFromRule(o,a)}))})}return e}extractSystemParams(t={}){return{optional:t?.[f.optional],description:t?.[f.description],summary:t?.[f.summary],deprecated:t?.[f.deprecated]}}removeExtensions(t){return Array.isArray(t)?t.map(e=>this.removeExtensions(e)):typeof t=="object"?(Object.values(f).forEach(e=>{delete t[e]}),Object.fromEntries(Object.entries(t).map(([e,r])=>[e,this.removeExtensions(r)]))):t}cleanComponents(t={}){return Object.fromEntries(Object.entries(t).map(([e,r])=>[e,Object.fromEntries(Object.entries(r).map(([n,s])=>s===!1?void 0:[n,s]).filter(Boolean))]))}handleTags(t,e,r=[]){let n=Array.from(new Set(r));return t.tags||(t.tags=[]),n.forEach(s=>{let a=e.get(s);!t.tags.some(({name:o})=>o===s)&&a&&t.tags.push(a)}),n}};var we=Me.Errors.MoleculerError,J={onlyLocal:!1,openapi:{info:{description:"",version:"0.0.1",title:"Api docs"},tags:[],paths:{},components:{schemas:Y.schemas,securitySchemes:{},responses:Y.responses},responses:{200:{$ref:"#/components/responses/ReturnedData"},401:{$ref:"#/components/responses/UnauthorizedError"},422:{$ref:"#/components/responses/ValidationError"},default:{$ref:"#/components/responses/ServerError"}}},cacheOpenApi:!0,skipUnresolvedActions:!0,cacheMode:"next-call",summaryTemplate:`{{summary}}
({{action}}) {{autoAlias}}`,returnAssetsAsStream:!0,defaultResponseContentType:_,multiPartFileFieldName:V,addServiceNameToTags:!1,UIOptions:{}},N=class{broker;settings;logger;validator;constructor(t,e){this.broker=t;let r=this.broker.validator;if(r.constructor.name!="FastestValidator"&&r.validator)throw new Error("only fastest validator is allowed");this.logger=this.broker.getLogger("moleculer-openapi-generator"),this.validator=r.validator,this.settings={...J,...e}}fetchServicesWithActions(t,e=!0,r=this.settings.onlyLocal){return t.call("$node.services",{withActions:e,onlyLocal:r??!1})}async mapAliases(t,e){this.logger.debug("mapAliases()");let r=e.filter(s=>s?.settings?.routes);if(this.logger.debug(`mapAliases() : ${r?.length??0} moleculer-web services found`),!r?.length)throw new we("fail to identify service hosting moleculer-web");let n=new C(this.logger);return(await Promise.all(r.map(async s=>await n.parse(t,s,this.settings.skipUnresolvedActions??!0,e)))).flat()}async getAliases(t){let e=await this.fetchServicesWithActions(t);return this.mapAliases(t,e)}async generateSchema(t,{filterAliasesFn:e,addMappers:r}){let n="3.1",s=await e(t,await this.getAliases(t)),a=new U(this.logger,this.validator,JSON.parse(JSON.stringify(this.settings.openapi)),r);return await a.load(),a.generate(n,s)}};import Ve from"moleculer";import K from"fs";import k from"path/posix";var Z=Ve.Errors.MoleculerError,P={},X={name:"openapi",settings:J,events:{async"$api.aliases.regenerated"(){let i="generateDocs",{cacheMode:t}=this.settings;if(t!=="timeout"&&this.broker.cacher&&this.actions[i]){let e=this.broker.cacher.getCacheKey(`${this.fullName}.${i}`,{},{},[]);await this.broker.cacher.clean(`${e}*`)}this.actions.regenerateOpenApiPaths().catch(e=>{this.logger.error(`regenerateOpenApiPaths failed with error : ${e.toString()}`)}),t==="refresh"&&await this.actions[i]()}},actions:{generateDocs:{rest:{path:"/openapi.json",method:"GET"},cache:{enabled(){return this.settings.cacheOpenApi??!0},keygen:(i,t)=>t.version?`${i}|${t?.version||re}`:i,ttl:600},openapi:{tags:["OpenApi"]},handler(i){return this.getGenerator().generateSchema(i,{filterAliasesFn:this.filterAliases,addMappers:this.addMappers})}},assets:{rest:{path:"/assets/:file",method:"GET"},openapi:{summary:"OpenAPI assets",description:"Return files from swagger-ui-dist folder",tags:["OpenApi"]},params:{file:{type:"enum",values:["swagger-ui.css","swagger-ui.css.map","swagger-ui-bundle.js","swagger-ui-bundle.js.map","swagger-ui-standalone-preset.js","swagger-ui-standalone-preset.js.map"]}},async handler(i){let{file:t}=i.params;t.indexOf(".css")>-1?i.meta.$responseType="text/css":t.indexOf(".js")>-1?i.meta.$responseType="text/javascript":i.meta.$responseType="application/octet-stream";let e=`${await this.getSwaggerPath()}/${t}`;return this.settings.returnAssetsAsStream?K.createReadStream(e):K.promises.readFile(e)}},ui:{rest:{path:"/ui",method:"GET"},openapi:{summary:"OpenAPI ui",description:"You can provide any schema file in query param",tags:["OpenApi"]},params:{url:{$$oa:{summary:"Schema file"},type:"string",optional:!0}},async handler(i){i.meta.$responseType="text/html; charset=utf-8";let t=await this.getOpenApiPaths(),e=t.assetsPath,r={swaggerSettings:{deepLinking:!0,showExtensions:!0,layout:"StandaloneLayout",...this.settings.UIOptions,url:i.params.url||t.schemaPath,dom_id:"#swagger-ui",oauth2RedirectUrl:t.oauth2RedirectPath},oauth:this.settings.UIOauthOptions};return`<html lang="en"><head><title>OpenAPI UI</title><style>body{ margin: 0;} </style></head><body><div id="swagger-ui"><p>Loading...</p><noscript>If you see json, you need to update your dependencies</noscript></div><script type="application/json" id="__SWAGGER_SETTINGS__">${JSON.stringify(r)} <\/script><script>var assetsURL="${e}"; var configElement=document.getElementById("__SWAGGER_SETTINGS__"); if (!configElement){ throw new Error("fail to load configurations");} var settings=JSON.parse(configElement.textContent); window.onload=function (){ var cssLink=document.createElement("link"); cssLink.rel="stylesheet"; cssLink.href=assetsURL + "/swagger-ui.css"; document.head.appendChild(cssLink); function initSwaggerUIDependentCode(){ var ui=SwaggerUIBundle( Object.assign(settings.swaggerSettings,{ presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset], plugins: [SwaggerUIBundle.plugins.DownloadUrl],}) ); if(settings.oauth){ ui.initOAuth(settings.oauth)}} var scripts=[assetsURL + "/swagger-ui-bundle.js", assetsURL + "/swagger-ui-standalone-preset.js"]; var scriptsLoaded=0; function loadScript(script, callback){ var scriptElement=document.createElement("script"); scriptElement.src=script; scriptElement.onload=()=>{ scriptsLoaded++; if (scriptsLoaded===scripts.length){ callback();}}; document.body.appendChild(scriptElement);} for (var i=0; i < scripts.length; i++){ loadScript(scripts[i], initSwaggerUIDependentCode);}}; <\/script></body></html>`}},oauth2Redirect:{rest:{path:"/oauth2-redirect",method:"GET"},openapi:{summary:"OpenAPI OAuth2 redirect",description:"This fill will handle the OAuth2",tags:["OpenApi"]},cache:!1,async handler(i){if(!this)throw new Z("unknown error");return i.meta.$responseType="text/html; charset=utf-8",K.promises.readFile(`${await this?.getSwaggerPath()}/oauth2-redirect.html`)}},regenerateOpenApiPaths:{visibility:"private",throttle:1e4,async handler(i){(await this.getGenerator().getAliases(i)).filter(e=>e.service?.name===this.name).forEach(e=>{e.action===`${this.name}.ui`&&(P.uiPath=e.fullPath),e.action===`${this.name}.assets`&&(P.assetsPath=e.fullPath?.replace("/:file","")),e.action===`${this.name}.oauth2Redirect`&&(P.oauth2RedirectPath=e.fullPath),e.action===`${this.name}.generateDocs`&&(P.schemaPath=e.fullPath)}),this.getOpenApiPaths()}}},methods:{getOpenApiPaths(){this.settings.schemaPath&&this.logger.warn("settings.schemaPath is deprecated, use settings.openApiPaths.schemaPath instead"),this.settings.assetsPath&&this.logger.warn("settings.assetsPath is deprecated, use settings.openApiPaths.assetsPath instead"),typeof this.settings.openApiPaths=="string"&&(this.settings.openApiPaths={schemaPath:k.join(this.settings.openApiPaths,"openapi.json"),uiPath:k.join(this.settings.openApiPaths,"ui"),oauth2RedirectPath:k.join(this.settings.openApiPaths,"oauth2-redirect"),assetsPath:k.join(this.settings.openApiPaths,"assets")});let i={assetsPath:this.settings.assetsPath??this.settings.openApiPaths?.assetsPath??P.assetsPath??se,schemaPath:this.settings.schemaPath??this.settings.openApiPaths?.schemaPath??P.schemaPath,uiPath:this.settings.openApiPaths?.uiPath??P.uiPath,oauth2RedirectPath:this.settings.openApiPaths?.oauth2RedirectPath??P.oauth2RedirectPath};return["assetsPath","schemaPath","uiPath","oauth2RedirectPath"].forEach(t=>{if(!i[t])throw new Z(`fail to get path for settings ${t}`)}),i},getSwaggerPath:async()=>{try{return(await import("swagger-ui-dist")).getAbsoluteFSPath()}catch{throw new Z("fail to load swagger ui")}},getGenerator(){if(!this.generator)throw new Error("no generator, bad initialization");return this.generator},filterAliases:(i,t)=>t,addMappers:(i,t)=>({})},created(){this.generator=new N(this.broker,this.settings)},async started(){this.logger.info("\u{1F4DC} OpenAPI Docs server is available")}};var Jt=X;var Kt=X;export{b as Alias,fe as ECacheMode,T as HTTP_METHODS,A as JOKER_METHOD,de as OPENAPI_VERSIONS_SUPPORTED,Kt as OpenApiMixin,v as PathAction,I as Route,Jt as default,X as mixin};