@cleverbrush/server-openapi 0.0.0-beta-20260415162610 → 0.0.0-beta-20260416114445
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.
- package/README.md +22 -9
- package/dist/generateOpenApiSpec.d.ts +21 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/openApiEndpoint.d.ts +14 -4
- package/dist/serveOpenApi.d.ts +12 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ const server = new ServerBuilder();
|
|
|
46
46
|
|
|
47
47
|
server
|
|
48
48
|
.use(serveOpenApi({
|
|
49
|
-
|
|
49
|
+
server,
|
|
50
50
|
info: { title: 'My API', version: '1.0.0' }
|
|
51
51
|
}))
|
|
52
52
|
.handle(GetUser, ({ params }) => ({ id: params.id }));
|
|
@@ -55,6 +55,8 @@ await server.listen(3000);
|
|
|
55
55
|
// GET /openapi.json → OpenAPI 3.1 document
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
When `server` is provided, endpoint registrations, authentication config, and webhooks are derived automatically. You can still pass `getRegistrations`, `authConfig`, or `webhooks` explicitly to override any server-derived value.
|
|
59
|
+
|
|
58
60
|
## Generating the Spec
|
|
59
61
|
|
|
60
62
|
### As middleware (recommended)
|
|
@@ -63,7 +65,7 @@ await server.listen(3000);
|
|
|
63
65
|
import { serveOpenApi } from '@cleverbrush/server-openapi';
|
|
64
66
|
|
|
65
67
|
server.use(serveOpenApi({
|
|
66
|
-
|
|
68
|
+
server,
|
|
67
69
|
info: { title: 'My API', version: '1.0.0' },
|
|
68
70
|
servers: [{ url: 'https://api.example.com', description: 'Production' }],
|
|
69
71
|
path: '/openapi.json' // default
|
|
@@ -76,7 +78,7 @@ server.use(serveOpenApi({
|
|
|
76
78
|
import { createOpenApiEndpoint } from '@cleverbrush/server-openapi';
|
|
77
79
|
|
|
78
80
|
const { endpoint: openApiEp, handler } = createOpenApiEndpoint({
|
|
79
|
-
|
|
81
|
+
server,
|
|
80
82
|
info: { title: 'My API', version: '1.0.0' }
|
|
81
83
|
});
|
|
82
84
|
|
|
@@ -313,20 +315,31 @@ When both `.returns()` and `.producesFile()` are set, the binary response takes
|
|
|
313
315
|
|
|
314
316
|
## Authentication & Security Schemes
|
|
315
317
|
|
|
316
|
-
|
|
318
|
+
When you pass the `server` option, authentication configuration is picked up automatically from `server.getAuthenticationConfig()`. Security schemes and per-operation `security` arrays are generated without any extra configuration:
|
|
317
319
|
|
|
318
320
|
```ts
|
|
319
321
|
import { jwtScheme } from '@cleverbrush/auth';
|
|
320
322
|
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
323
|
+
const server = new ServerBuilder()
|
|
324
|
+
.useAuthentication({
|
|
325
|
+
defaultScheme: 'jwt',
|
|
326
|
+
schemes: [jwtScheme({ secret: '...', mapClaims: c => c })]
|
|
327
|
+
})
|
|
328
|
+
.useAuthorization();
|
|
325
329
|
|
|
330
|
+
server.use(serveOpenApi({
|
|
331
|
+
server,
|
|
332
|
+
info: { title: 'My API', version: '1.0.0' }
|
|
333
|
+
}));
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
You can also pass `authConfig` explicitly (useful when not using the `server` option):
|
|
337
|
+
|
|
338
|
+
```ts
|
|
326
339
|
server.use(serveOpenApi({
|
|
327
340
|
getRegistrations: () => server.getRegistrations(),
|
|
328
341
|
info: { title: 'My API', version: '1.0.0' },
|
|
329
|
-
authConfig
|
|
342
|
+
authConfig: server.getAuthenticationConfig()
|
|
330
343
|
}));
|
|
331
344
|
```
|
|
332
345
|
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { AuthenticationConfig, EndpointRegistration, WebhookDefinition } from '@cleverbrush/server';
|
|
2
2
|
import { type OpenApiSecurityScheme } from './securityMapper.js';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal interface for a `@cleverbrush/server` server instance. Matches
|
|
5
|
+
* the relevant subset of `ServerBuilder` so that callers can pass the
|
|
6
|
+
* server directly without importing the class.
|
|
7
|
+
*/
|
|
8
|
+
export interface OpenApiServer_ServerLike {
|
|
9
|
+
getRegistrations(): readonly EndpointRegistration[];
|
|
10
|
+
getAuthenticationConfig(): AuthenticationConfig | null;
|
|
11
|
+
getWebhooks(): readonly WebhookDefinition[];
|
|
12
|
+
}
|
|
3
13
|
/**
|
|
4
14
|
* API metadata included in the OpenAPI `info` object.
|
|
5
15
|
* Maps directly to the OpenAPI 3.1 Info Object.
|
|
@@ -47,9 +57,19 @@ export interface OpenApiTag {
|
|
|
47
57
|
}
|
|
48
58
|
/**
|
|
49
59
|
* Options passed to {@link generateOpenApiSpec}.
|
|
60
|
+
*
|
|
61
|
+
* When `server` is provided, `registrations` and `authConfig` are derived
|
|
62
|
+
* from it automatically (unless explicitly overridden).
|
|
50
63
|
*/
|
|
51
64
|
export interface OpenApiOptions {
|
|
52
|
-
|
|
65
|
+
/**
|
|
66
|
+
* A `ServerBuilder` (or any object implementing the same methods).
|
|
67
|
+
* When set, `registrations`, `authConfig`, and `webhooks` are
|
|
68
|
+
* automatically read from the server instance. Explicit values for
|
|
69
|
+
* those fields take precedence over the server-derived ones.
|
|
70
|
+
*/
|
|
71
|
+
readonly server?: OpenApiServer_ServerLike;
|
|
72
|
+
readonly registrations?: readonly EndpointRegistration[];
|
|
53
73
|
readonly info: OpenApiInfo;
|
|
54
74
|
readonly servers?: readonly OpenApiServer[];
|
|
55
75
|
readonly authConfig?: AuthenticationConfig | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { writeOpenApiSpec } from './cli.js';
|
|
2
|
-
export { generateOpenApiSpec, type OpenApiDocument, type OpenApiInfo, type OpenApiOptions, type OpenApiServer, type OpenApiTag } from './generateOpenApiSpec.js';
|
|
2
|
+
export { generateOpenApiSpec, type OpenApiDocument, type OpenApiInfo, type OpenApiOptions, type OpenApiServer, type OpenApiServer_ServerLike, type OpenApiTag } from './generateOpenApiSpec.js';
|
|
3
3
|
export { createOpenApiEndpoint, type OpenApiEndpointOptions } from './openApiEndpoint.js';
|
|
4
4
|
export { type PathParameterInfo, type ResolvedPath, resolvePath } from './pathUtils.js';
|
|
5
5
|
export { convertSchema } from './schemaConverter.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as A from"fs";import*as F from"path";import{ObjectSchemaBuilder as M,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as P}from"@cleverbrush/schema";import{toJsonSchema as U}from"@cleverbrush/schema-json";function $(e){return typeof e!="string"&&typeof e.validate=="function"}function _(e){let t=[];return{converted:e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(n,o)=>(t.push(o),`{${o}}`)),paramNames:t}}function L(e){let t=e.introspect(),r=t.templateDefinition,i=t.objectSchema.introspect().properties??{},d="";for(let p=0;p<r.segments.length;p++)d+=r.literals[p]+`{${r.segments[p].path}}`;d+=r.literals[r.segments.length]??"";let a=r.segments.map(p=>({name:p.path,schema:i[p.path]?U(i[p.path],{$schema:!1}):{type:"string"}}));return{pathString:d,parameters:a}}function B(e){let t=e.basePath.replace(/\/$/,""),r=e.pathTemplate;if($(r)){let{pathString:s,parameters:y}=L(r);return{path:T(t+s),parameters:y}}let o=t+(r==="/"?"":r),{converted:i,paramNames:d}=_(o),a=T(i),p=d.map(s=>({name:s,schema:{type:"string"}}));return{path:a,parameters:p}}function T(e){let t=e.replace(/\/+/g,"/");return t.startsWith("/")||(t="/"+t),t.length>1&&t.endsWith("/")&&(t=t.slice(0,-1)),t||"/"}import{toJsonSchema as H}from"@cleverbrush/schema-json";function b(e,t){if(e==null)return{};let r;return t&&(typeof t=="function"?r=n=>t(n)??null:r=n=>t.getName(n)),H(e,{$schema:!1,draft:"2020-12",nameResolver:r})}var j=class{byInstance=new Map;byName=new Map;register(t){let r=t.introspect().schemaName;if(typeof r!="string")return;let n=this.byName.get(r);if(n!==void 0){if(n===t)return;throw new Error(`Schema name "${r}" is already registered by a different schema instance. Each named schema must be a single, reused constant. If you intended to register the same schema, ensure you are passing the same object reference (not a rebuilt schema).`)}this.byInstance.set(t,r),this.byName.set(r,t)}getName(t){return this.byInstance.get(t)??null}entries(){return this.byName.entries()}get isEmpty(){return this.byName.size===0}};function m(e,t,r=new Set){if(r.has(e))return;r.add(e),t.register(e);let n=e.introspect();switch(n.type){case"object":{let o=n.properties;if(o)for(let i of Object.values(o))m(i,t,r);break}case"array":n.elementSchema&&m(n.elementSchema,t,r);break;case"tuple":{let o=n.elements??[];for(let i of o)m(i,t,r);n.restSchema&&m(n.restSchema,t,r);break}case"union":{let o=n.options??[];for(let i of o)m(i,t,r);break}case"record":n.keySchema&&m(n.keySchema,t,r),n.valueSchema&&m(n.valueSchema,t,r);break;case"lazy":{let o=e.resolve();m(o,t,r);break}default:break}}function x(e){if(!e)return{};let t={};for(let r of e.schemes){let n=r.name,o=r.challenge?.();if("openIdConnectUrl"in r&&r.openIdConnectUrl)t[n]={type:"openIdConnect",openIdConnectUrl:r.openIdConnectUrl};else if("flows"in r&&r.flows)t[n]={type:"oauth2",flows:r.flows};else if(o?.headerValue?.toLowerCase().startsWith("bearer")||n==="jwt")t[n]={type:"http",scheme:"bearer",bearerFormat:"JWT"};else if(n==="cookie"||r.cookieName){let i=r.cookieName??r._options?.cookieName??"session";t[n]={type:"apiKey",in:"cookie",name:i}}else o?t[n]={type:"http",scheme:o.headerValue.split(" ")[0].toLowerCase()}:t[n]={type:"http",scheme:n}}return t}function C(e,t){return e===null?[]:t.length===0?[]:t.map(r=>({[r]:[...e]}))}function E(e,t,r,n,o){let i={name:e,in:t,schema:r};return n&&(i.required=!0),o&&(i.description=o),i}function D(e,t,r,n){let o=b(e,t),i=e.introspect(),d={schema:o};r!=null?d.example=r:n!=null&&(d.examples=n);let a={required:i.isRequired!==!1,content:{"application/json":d}};return typeof i.description=="string"&&i.description!==""&&(a.description=i.description),a}var z={200:"OK",201:"Created",202:"Accepted",204:"No Content",400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",409:"Conflict",422:"Unprocessable Entity",500:"Internal Server Error"},N={type:"object",properties:{status:{type:"integer"},title:{type:"string"},detail:{type:"string"}}};function J(e,t){let n=e.introspect().properties??{},o={};for(let[i,d]of Object.entries(n)){let a=d.introspect(),p={schema:b(d,t)};typeof a.description=="string"&&a.description!==""&&(p.description=a.description),o[i]=p}return o}function K(e,t,r){let n={};if(e.producesFile){let o=e.producesFile.contentType??"application/octet-stream",i=e.producesFile.description??"File download";n[200]={description:i,content:{[o]:{schema:{type:"string",format:"binary"}}}}}else if(e.responsesSchemas)for(let[o,i]of Object.entries(e.responsesSchemas)){let d=Number(o),a=z[d]??`Response ${o}`;if(i){let p=b(i,r),s=i.introspect(),y=typeof s.description=="string"&&s.description!==""?s.description:a;n[o]={description:y,content:{"application/json":{schema:p}}}}else n[o]={description:a}}else if(e.responseSchema){let o=b(e.responseSchema,r),i=e.responseSchema.introspect(),d=typeof i.description=="string"&&i.description!==""?i.description:"Successful response";n[200]={description:d,content:{"application/json":{schema:o}}}}else t==="DELETE"||t==="HEAD"?n[204]={description:"No content"}:n[200]={description:"Successful response"};if(e.bodySchema&&!n[422]&&(n[422]={description:"Validation error",content:{"application/problem+json":{schema:N}}}),e.authRoles!==null&&(n[401]||(n[401]={description:"Unauthorized",content:{"application/problem+json":{schema:N}}}),n[403]||(n[403]={description:"Forbidden",content:{"application/problem+json":{schema:N}}})),e.produces&&!e.producesFile)for(let o of Object.keys(n)){let d=n[o].content;if(!d||d["application/problem+json"])continue;let a=d["application/json"]?.schema??{};for(let[p,s]of Object.entries(e.produces))p!=="application/json"&&(d[p]={schema:s.schema?b(s.schema,r):a})}if(e.responseHeaderSchema){let o=J(e.responseHeaderSchema,r);for(let i of Object.keys(n))n[i].headers=o}if(e.links){let o={};for(let[d,a]of Object.entries(e.links)){let p={operationId:a.operationId};if(a.description&&(p.description=a.description),a.requestBody&&(p.requestBody=a.requestBody),a.parameters){let s;if(typeof a.parameters=="function"&&e.responseSchema){let y=M.getPropertiesFor(e.responseSchema),h=a.parameters(y);s={};for(let[f,g]of Object.entries(h))if(g&&typeof g=="object"&&P in g){let O=g[P].toJsonPointer();s[f]=`$response.body#${O}`}else s[f]=g}else s=a.parameters;p.parameters=s}o[d]=p}let i=Object.keys(n).filter(d=>Number(d)>=200&&Number(d)<300).sort()[0];i&&(n[i].links=o)}return n}function W(e,t,r,n){let o={};e.summary&&(o.summary=e.summary),e.description&&(o.description=e.description),e.tags.length>0&&(o.tags=[...e.tags]),e.operationId&&(o.operationId=e.operationId),e.deprecated&&(o.deprecated=!0);let i=[];for(let a of t)i.push(E(a.name,"path",a.schema,!0));if(e.querySchema){let p=e.querySchema.introspect().properties??{};for(let[s,y]of Object.entries(p)){let h=y.introspect(),f=h.isRequired!==!1,g=typeof h.description=="string"&&h.description!==""?h.description:void 0;i.push(E(s,"query",b(y,n),f,g))}}if(e.headerSchema){let p=e.headerSchema.introspect().properties??{};for(let[s,y]of Object.entries(p)){let h=y.introspect(),f=h.isRequired!==!1,g=typeof h.description=="string"&&h.description!==""?h.description:void 0;i.push(E(s,"header",b(y,n),f,g))}}i.length>0&&(o.parameters=i),e.bodySchema&&(o.requestBody=D(e.bodySchema,n,e.example,e.examples)),o.responses=K(e,e.method.toUpperCase(),n);let d=C(V(e),r);if(d.length>0&&(o.security=d),e.externalDocs){let a={url:e.externalDocs.url};e.externalDocs.description&&(a.description=e.externalDocs.description),o.externalDocs=a}if(e.callbacks){let a={};for(let[p,s]of Object.entries(e.callbacks)){let y;if(s.expression)y=s.expression;else if(s.urlFrom&&e.bodySchema){let O=M.getPropertiesFor(e.bodySchema),R=s.urlFrom(O);R&&typeof R=="object"&&P in R?y=`{$request.body#${R[P].toJsonPointer()}}`:y=String(R??"{$request.body#/callbackUrl}")}else y="{$request.body#/callbackUrl}";let h=(s.method??"post").toLowerCase(),f={};s.summary&&(f.summary=s.summary),s.description&&(f.description=s.description),s.body&&(f.requestBody=D(s.body,n));let g={};s.response?g[200]={description:"OK",content:{"application/json":{schema:b(s.response,n)}}}:g[200]={description:"OK"},f.responses=g,a[p]={[y]:{[h]:f}}}o.callbacks=a}return o}function V(e){return e.authRoles}function w(e){let{registrations:t,info:r,servers:n,authConfig:o,securitySchemes:i,tags:d}=e,a=i??x(o),p=Object.keys(a),s=new j,y=new Set;for(let l of t){let c=l.endpoint;if(c.bodySchema&&m(c.bodySchema,s,y),c.responseSchema&&m(c.responseSchema,s,y),c.responsesSchemas)for(let u of Object.values(c.responsesSchemas))u&&m(u,s,y);if(c.querySchema){let u=c.querySchema.introspect().properties??{};for(let S of Object.values(u))m(S,s,y)}if(c.headerSchema){let u=c.headerSchema.introspect().properties??{};for(let S of Object.values(u))m(S,s,y)}if(c.callbacks)for(let u of Object.values(c.callbacks))u.body&&m(u.body,s,y),u.response&&m(u.response,s,y)}if(e.webhooks)for(let l of e.webhooks)l.body&&m(l.body,s,y),l.response&&m(l.response,s,y);let h=l=>{let c=!1;return u=>{if(u===l&&!c){c=!0;return}return s.getName(u)??void 0}},f={};for(let l of t){let c=l.endpoint,{path:u,parameters:S}=B(c),v=c.method.toLowerCase();f[u]||(f[u]={}),f[u][v]=W(c,S,p,s)}let g=new Set((d??[]).map(l=>l.name)),O=[];for(let l of t)for(let c of l.endpoint.tags)!g.has(c)&&!O.includes(c)&&O.push(c);O.sort();let R=[...d??[],...O.map(l=>({name:l}))],k={openapi:"3.1.0",info:{...r}};if(n&&n.length>0&&(k.servers=n.map(l=>({...l}))),R.length>0&&(k.tags=R.map(l=>({...l}))),k.paths=f,e.webhooks&&e.webhooks.length>0){let l={};for(let c of e.webhooks){let u=(c.method??"post").toLowerCase(),S={};c.summary&&(S.summary=c.summary),c.description&&(S.description=c.description),c.tags&&c.tags.length>0&&(S.tags=[...c.tags]),c.body&&(S.requestBody=D(c.body,s));let v={};c.response?v[200]={description:"OK",content:{"application/json":{schema:b(c.response,s)}}}:v[200]={description:"OK"},S.responses=v,l[c.name]={[u]:S}}k.webhooks=l}let I={};for(let[l,c]of s.entries())I[l]=b(c,h(c));let q=Object.keys(I).length>0;if(p.length>0||q){let l={};p.length>0&&(l.securitySchemes={...a}),q&&(l.schemas=I),k.components=l}return k}function Y(e,t){let r=w(e),n=F.dirname(t);return n&&!A.existsSync(n)&&A.mkdirSync(n,{recursive:!0}),A.writeFileSync(t,JSON.stringify(r,null,2),"utf-8"),r}import{endpoint as Z}from"@cleverbrush/server";function G(e){let t=e.path??"/openapi.json",r=Z.get(t).summary("OpenAPI specification").tags("OpenAPI").operationId("getOpenApiSpec"),n=null;return{endpoint:r,handler:()=>(n||(n=w({registrations:e.getRegistrations(),info:e.info,servers:e.servers,authConfig:e.authConfig,securitySchemes:e.securitySchemes})),n)}}function Q(e){let t=e.path??"/openapi.json",r=null;return async(n,o)=>{let d=n.url.pathname;if(n.method.toUpperCase()==="GET"&&d===t){r||(r=w({registrations:e.getRegistrations(),info:e.info,servers:e.servers,authConfig:e.authConfig,securitySchemes:e.securitySchemes}));let p=JSON.stringify(r);n.response.writeHead(200,{"content-type":"application/json","content-length":Buffer.byteLength(p).toString()}),n.response.end(p);return}await o()}}export{j as SchemaRegistry,b as convertSchema,G as createOpenApiEndpoint,w as generateOpenApiSpec,C as mapOperationSecurity,x as mapSecuritySchemes,B as resolvePath,Q as serveOpenApi,m as walkSchemas,Y as writeOpenApiSpec};
|
|
1
|
+
import*as v from"fs";import*as F from"path";import{ObjectSchemaBuilder as M,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as C}from"@cleverbrush/schema";import{toJsonSchema as U}from"@cleverbrush/schema-json";function $(e){return typeof e!="string"&&typeof e.validate=="function"}function W(e){let r=[];return{converted:e.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(n,o)=>(r.push(o),`{${o}}`)),paramNames:r}}function H(e){let r=e.introspect(),t=r.templateDefinition,s=r.objectSchema.introspect().properties??{},p="";for(let d=0;d<t.segments.length;d++)p+=t.literals[d]+`{${t.segments[d].path}}`;p+=t.literals[t.segments.length]??"";let a=t.segments.map(d=>({name:d.path,schema:s[d.path]?U(s[d.path],{$schema:!1}):{type:"string"}}));return{pathString:p,parameters:a}}function B(e){let r=e.basePath.replace(/\/$/,""),t=e.pathTemplate;if($(t)){let{pathString:c,parameters:u}=H(t);return{path:L(r+c),parameters:u}}let o=r+(t==="/"?"":t),{converted:s,paramNames:p}=W(o),a=L(s),d=p.map(c=>({name:c,schema:{type:"string"}}));return{path:a,parameters:d}}function L(e){let r=e.replace(/\/+/g,"/");return r.startsWith("/")||(r="/"+r),r.length>1&&r.endsWith("/")&&(r=r.slice(0,-1)),r||"/"}import{toJsonSchema as z}from"@cleverbrush/schema-json";function b(e,r){if(e==null)return{};let t;return r&&(typeof r=="function"?t=n=>r(n)??null:t=n=>r.getName(n)),z(e,{$schema:!1,draft:"2020-12",nameResolver:t})}var P=class{byInstance=new Map;byName=new Map;register(r){let t=r.introspect().schemaName;if(typeof t!="string")return;let n=this.byName.get(t);if(n!==void 0){if(n===r)return;throw new Error(`Schema name "${t}" is already registered by a different schema instance. Each named schema must be a single, reused constant. If you intended to register the same schema, ensure you are passing the same object reference (not a rebuilt schema).`)}this.byInstance.set(r,t),this.byName.set(t,r)}getName(r){return this.byInstance.get(r)??null}entries(){return this.byName.entries()}get isEmpty(){return this.byName.size===0}};function h(e,r,t=new Set){if(t.has(e))return;t.add(e),r.register(e);let n=e.introspect();switch(n.type){case"object":{let o=n.properties;if(o)for(let s of Object.values(o))h(s,r,t);break}case"array":n.elementSchema&&h(n.elementSchema,r,t);break;case"tuple":{let o=n.elements??[];for(let s of o)h(s,r,t);n.restSchema&&h(n.restSchema,r,t);break}case"union":{let o=n.options??[];for(let s of o)h(s,r,t);break}case"record":n.keySchema&&h(n.keySchema,r,t),n.valueSchema&&h(n.valueSchema,r,t);break;case"lazy":{let o=e.resolve();h(o,r,t);break}default:break}}function x(e){if(!e)return{};let r={};for(let t of e.schemes){let n=t.name,o=t.challenge?.();if("openIdConnectUrl"in t&&t.openIdConnectUrl)r[n]={type:"openIdConnect",openIdConnectUrl:t.openIdConnectUrl};else if("flows"in t&&t.flows)r[n]={type:"oauth2",flows:t.flows};else if(o?.headerValue?.toLowerCase().startsWith("bearer")||n==="jwt")r[n]={type:"http",scheme:"bearer",bearerFormat:"JWT"};else if(n==="cookie"||t.cookieName){let s=t.cookieName??t._options?.cookieName??"session";r[n]={type:"apiKey",in:"cookie",name:s}}else o?r[n]={type:"http",scheme:o.headerValue.split(" ")[0].toLowerCase()}:r[n]={type:"http",scheme:n}}return r}function E(e,r){return e===null?[]:r.length===0?[]:r.map(t=>({[t]:[...e]}))}function N(e,r,t,n,o){let s={name:e,in:r,schema:t};return n&&(s.required=!0),o&&(s.description=o),s}function q(e,r,t,n){let o=b(e,r),s=e.introspect(),p={schema:o};t!=null?p.example=t:n!=null&&(p.examples=n);let a={required:s.isRequired!==!1,content:{"application/json":p}};return typeof s.description=="string"&&s.description!==""&&(a.description=s.description),a}var J={200:"OK",201:"Created",202:"Accepted",204:"No Content",400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",409:"Conflict",422:"Unprocessable Entity",500:"Internal Server Error"},D={type:"object",properties:{status:{type:"integer"},title:{type:"string"},detail:{type:"string"}}};function K(e,r){let n=e.introspect().properties??{},o={};for(let[s,p]of Object.entries(n)){let a=p.introspect(),d={schema:b(p,r)};typeof a.description=="string"&&a.description!==""&&(d.description=a.description),o[s]=d}return o}function V(e,r,t){let n={};if(e.producesFile){let o=e.producesFile.contentType??"application/octet-stream",s=e.producesFile.description??"File download";n[200]={description:s,content:{[o]:{schema:{type:"string",format:"binary"}}}}}else if(e.responsesSchemas)for(let[o,s]of Object.entries(e.responsesSchemas)){let p=Number(o),a=J[p]??`Response ${o}`;if(s){let d=b(s,t),c=s.introspect(),u=typeof c.description=="string"&&c.description!==""?c.description:a;n[o]={description:u,content:{"application/json":{schema:d}}}}else n[o]={description:a}}else if(e.responseSchema){let o=b(e.responseSchema,t),s=e.responseSchema.introspect(),p=typeof s.description=="string"&&s.description!==""?s.description:"Successful response";n[200]={description:p,content:{"application/json":{schema:o}}}}else r==="DELETE"||r==="HEAD"?n[204]={description:"No content"}:n[200]={description:"Successful response"};if(e.bodySchema&&!n[422]&&(n[422]={description:"Validation error",content:{"application/problem+json":{schema:D}}}),e.authRoles!==null&&(n[401]||(n[401]={description:"Unauthorized",content:{"application/problem+json":{schema:D}}}),n[403]||(n[403]={description:"Forbidden",content:{"application/problem+json":{schema:D}}})),e.produces&&!e.producesFile)for(let o of Object.keys(n)){let p=n[o].content;if(!p||p["application/problem+json"])continue;let a=p["application/json"]?.schema??{};for(let[d,c]of Object.entries(e.produces))d!=="application/json"&&(p[d]={schema:c.schema?b(c.schema,t):a})}if(e.responseHeaderSchema){let o=K(e.responseHeaderSchema,t);for(let s of Object.keys(n))n[s].headers=o}if(e.links){let o={};for(let[p,a]of Object.entries(e.links)){let d={operationId:a.operationId};if(a.description&&(d.description=a.description),a.requestBody&&(d.requestBody=a.requestBody),a.parameters){let c;if(typeof a.parameters=="function"&&e.responseSchema){let u=M.getPropertiesFor(e.responseSchema),l=a.parameters(u);c={};for(let[f,g]of Object.entries(l))if(g&&typeof g=="object"&&C in g){let O=g[C].toJsonPointer();c[f]=`$response.body#${O}`}else c[f]=g}else c=a.parameters;d.parameters=c}o[p]=d}let s=Object.keys(n).filter(p=>Number(p)>=200&&Number(p)<300).sort()[0];s&&(n[s].links=o)}return n}function Y(e,r,t,n){let o={};e.summary&&(o.summary=e.summary),e.description&&(o.description=e.description),e.tags.length>0&&(o.tags=[...e.tags]),e.operationId&&(o.operationId=e.operationId),e.deprecated&&(o.deprecated=!0);let s=[];for(let a of r)s.push(N(a.name,"path",a.schema,!0));if(e.querySchema){let d=e.querySchema.introspect().properties??{};for(let[c,u]of Object.entries(d)){let l=u.introspect(),f=l.isRequired!==!1,g=typeof l.description=="string"&&l.description!==""?l.description:void 0;s.push(N(c,"query",b(u,n),f,g))}}if(e.headerSchema){let d=e.headerSchema.introspect().properties??{};for(let[c,u]of Object.entries(d)){let l=u.introspect(),f=l.isRequired!==!1,g=typeof l.description=="string"&&l.description!==""?l.description:void 0;s.push(N(c,"header",b(u,n),f,g))}}s.length>0&&(o.parameters=s),e.bodySchema&&(o.requestBody=q(e.bodySchema,n,e.example,e.examples)),o.responses=V(e,e.method.toUpperCase(),n);let p=E(Z(e),t);if(p.length>0&&(o.security=p),e.externalDocs){let a={url:e.externalDocs.url};e.externalDocs.description&&(a.description=e.externalDocs.description),o.externalDocs=a}if(e.callbacks){let a={};for(let[d,c]of Object.entries(e.callbacks)){let u;if(c.expression)u=c.expression;else if(c.urlFrom&&e.bodySchema){let O=M.getPropertiesFor(e.bodySchema),R=c.urlFrom(O);R&&typeof R=="object"&&C in R?u=`{$request.body#${R[C].toJsonPointer()}}`:u=String(R??"{$request.body#/callbackUrl}")}else u="{$request.body#/callbackUrl}";let l=(c.method??"post").toLowerCase(),f={};c.summary&&(f.summary=c.summary),c.description&&(f.description=c.description),c.body&&(f.requestBody=q(c.body,n));let g={};c.response?g[200]={description:"OK",content:{"application/json":{schema:b(c.response,n)}}}:g[200]={description:"OK"},f.responses=g,a[d]={[u]:{[l]:f}}}o.callbacks=a}return o}function Z(e){return e.authRoles}function k(e){let r=e.server,t=e.registrations??r?.getRegistrations()??[],n=e.authConfig!==void 0?e.authConfig:r?.getAuthenticationConfig()??void 0,o=e.webhooks??(r&&r.getWebhooks().length>0?r.getWebhooks():void 0),{info:s,servers:p,securitySchemes:a,tags:d}=e,c=a??x(n),u=Object.keys(c),l=new P,f=new Set;for(let y of t){let i=y.endpoint;if(i.bodySchema&&h(i.bodySchema,l,f),i.responseSchema&&h(i.responseSchema,l,f),i.responsesSchemas)for(let m of Object.values(i.responsesSchemas))m&&h(m,l,f);if(i.querySchema){let m=i.querySchema.introspect().properties??{};for(let S of Object.values(m))h(S,l,f)}if(i.headerSchema){let m=i.headerSchema.introspect().properties??{};for(let S of Object.values(m))h(S,l,f)}if(i.callbacks)for(let m of Object.values(i.callbacks))m.body&&h(m.body,l,f),m.response&&h(m.response,l,f)}if(o)for(let y of o)y.body&&h(y.body,l,f),y.response&&h(y.response,l,f);let g=y=>{let i=!1;return m=>{if(m===y&&!i){i=!0;return}return l.getName(m)??void 0}},O={};for(let y of t){let i=y.endpoint,{path:m,parameters:S}=B(i),j=i.method.toLowerCase();O[m]||(O[m]={}),O[m][j]=Y(i,S,u,l)}let R=new Set((d??[]).map(y=>y.name)),w=[];for(let y of t)for(let i of y.endpoint.tags)!R.has(i)&&!w.includes(i)&&w.push(i);w.sort();let T=[...d??[],...w.map(y=>({name:y}))],A={openapi:"3.1.0",info:{...s}};if(p&&p.length>0&&(A.servers=p.map(y=>({...y}))),T.length>0&&(A.tags=T.map(y=>({...y}))),A.paths=O,o&&o.length>0){let y={};for(let i of o){let m=(i.method??"post").toLowerCase(),S={};i.summary&&(S.summary=i.summary),i.description&&(S.description=i.description),i.tags&&i.tags.length>0&&(S.tags=[...i.tags]),i.body&&(S.requestBody=q(i.body,l));let j={};i.response?j[200]={description:"OK",content:{"application/json":{schema:b(i.response,l)}}}:j[200]={description:"OK"},S.responses=j,y[i.name]={[m]:S}}A.webhooks=y}let I={};for(let[y,i]of l.entries())I[y]=b(i,g(i));let _=Object.keys(I).length>0;if(u.length>0||_){let y={};u.length>0&&(y.securitySchemes={...c}),_&&(y.schemas=I),A.components=y}return A}function G(e,r){let t=k(e),n=F.dirname(r);return n&&!v.existsSync(n)&&v.mkdirSync(n,{recursive:!0}),v.writeFileSync(r,JSON.stringify(t,null,2),"utf-8"),t}import{endpoint as Q}from"@cleverbrush/server";function X(e){let r=e.path??"/openapi.json",t=e.server,n=e.getRegistrations??(t?()=>t.getRegistrations():()=>[]),o=Q.get(r).summary("OpenAPI specification").tags("OpenAPI").operationId("getOpenApiSpec"),s=null;return{endpoint:o,handler:()=>(s||(s=k({registrations:n(),info:e.info,servers:e.servers,authConfig:e.authConfig!==void 0?e.authConfig:t?.getAuthenticationConfig()??void 0,securitySchemes:e.securitySchemes})),s)}}function ee(e){let r=e.path??"/openapi.json",t=e.server,n=e.getRegistrations??(t?()=>t.getRegistrations():()=>[]),o=null;return async(s,p)=>{let d=s.url.pathname;if(s.method.toUpperCase()==="GET"&&d===r){o||(o=k({registrations:n(),info:e.info,servers:e.servers,authConfig:e.authConfig!==void 0?e.authConfig:t?.getAuthenticationConfig()??void 0,securitySchemes:e.securitySchemes}));let u=JSON.stringify(o);s.response.writeHead(200,{"content-type":"application/json","content-length":Buffer.byteLength(u).toString()}),s.response.end(u);return}await p()}}export{P as SchemaRegistry,b as convertSchema,X as createOpenApiEndpoint,k as generateOpenApiSpec,E as mapOperationSecurity,x as mapSecuritySchemes,B as resolvePath,ee as serveOpenApi,h as walkSchemas,G as writeOpenApiSpec};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/generateOpenApiSpec.ts","../src/pathUtils.ts","../src/schemaConverter.ts","../src/schemaRegistry.ts","../src/securityMapper.ts","../src/openApiEndpoint.ts","../src/serveOpenApi.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport {\n generateOpenApiSpec,\n type OpenApiDocument,\n type OpenApiOptions\n} from './generateOpenApiSpec.js';\n\n/**\n * Generate an OpenAPI spec and write it to a file.\n *\n * @param options - Same options as `generateOpenApiSpec()`.\n * @param outputPath - File path to write the JSON spec to.\n * @returns The generated spec document.\n *\n * @example\n * ```ts\n * import { writeOpenApiSpec } from '@cleverbrush/server-openapi';\n * import { createServer, endpoint } from '@cleverbrush/server';\n *\n * const builder = createServer()\n * .handle(endpoint.get('/api/health'), () => ({ ok: true }));\n *\n * writeOpenApiSpec(\n * {\n * registrations: builder.getRegistrations(),\n * info: { title: 'My API', version: '1.0.0' }\n * },\n * './openapi.json'\n * );\n * ```\n */\nexport function writeOpenApiSpec(\n options: OpenApiOptions,\n outputPath: string\n): OpenApiDocument {\n const spec = generateOpenApiSpec(options);\n const dir = path.dirname(outputPath);\n if (dir && !fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2), 'utf-8');\n return spec;\n}\n","import {\n ObjectSchemaBuilder,\n type SchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport type {\n AuthenticationConfig,\n EndpointMetadata,\n EndpointRegistration,\n WebhookDefinition\n} from '@cleverbrush/server';\nimport { resolvePath } from './pathUtils.js';\nimport { convertSchema } from './schemaConverter.js';\nimport { SchemaRegistry, walkSchemas } from './schemaRegistry.js';\nimport {\n mapOperationSecurity,\n mapSecuritySchemes,\n type OpenApiSecurityScheme\n} from './securityMapper.js';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * API metadata included in the OpenAPI `info` object.\n * Maps directly to the OpenAPI 3.1 Info Object.\n */\nexport interface OpenApiInfo {\n readonly title: string;\n readonly version: string;\n readonly description?: string;\n readonly termsOfService?: string;\n readonly contact?: {\n readonly name?: string;\n readonly url?: string;\n readonly email?: string;\n };\n readonly license?: {\n readonly name: string;\n readonly url?: string;\n };\n}\n\n/**\n * A server entry in the OpenAPI `servers` array.\n * Describes a base URL where the API is accessible.\n */\nexport interface OpenApiServer {\n readonly url: string;\n readonly description?: string;\n}\n\n/**\n * A tag entry in the OpenAPI top-level `tags` array.\n * Provides a name, optional description, and optional external documentation\n * for a tag group.\n *\n * @see https://spec.openapis.org/oas/v3.1.0#tag-object\n */\nexport interface OpenApiTag {\n /** Tag name. Must match the tag strings used on individual operations. */\n readonly name: string;\n /** Short description for the tag group, displayed in Swagger UI / Redoc. */\n readonly description?: string;\n /** Link to external documentation for this tag. */\n readonly externalDocs?: {\n readonly url: string;\n readonly description?: string;\n };\n}\n\n/**\n * Options passed to {@link generateOpenApiSpec}.\n */\nexport interface OpenApiOptions {\n readonly registrations: readonly EndpointRegistration[];\n readonly info: OpenApiInfo;\n readonly servers?: readonly OpenApiServer[];\n readonly authConfig?: AuthenticationConfig | null;\n readonly securitySchemes?: Record<string, OpenApiSecurityScheme>;\n /**\n * Top-level tag definitions with optional descriptions and external docs.\n *\n * When provided, these entries are emitted as the top-level `tags` array.\n * Any tag names used by registered endpoints but absent from this list are\n * automatically appended as name-only entries (sorted alphabetically).\n *\n * When omitted, unique tag names are still auto-collected from all\n * registered endpoints and emitted as name-only entries.\n *\n * @see https://spec.openapis.org/oas/v3.1.0#tag-object\n */\n readonly tags?: readonly OpenApiTag[];\n /**\n * Webhook definitions to emit in the top-level `webhooks` map of the\n * generated OpenAPI document.\n *\n * Webhooks are not served as HTTP routes — they merely document async\n * out-of-band requests that your API sends to subscribers.\n *\n * @see https://spec.openapis.org/oas/v3.1.0#fixed-fields\n */\n readonly webhooks?: readonly WebhookDefinition[];\n}\n\n// ---------------------------------------------------------------------------\n// OpenAPI Document (partial typing — plain objects for flexibility)\n// ---------------------------------------------------------------------------\n\n/**\n * A generated OpenAPI 3.1 document. Typed as a plain object map to allow\n * any extension fields without requiring a full OpenAPI type library.\n */\nexport type OpenApiDocument = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Generator\n// ---------------------------------------------------------------------------\n\nfunction buildParameterObject(\n name: string,\n location: 'query' | 'header' | 'path',\n schema: Record<string, unknown>,\n required: boolean,\n description?: string\n): Record<string, unknown> {\n const param: Record<string, unknown> = {\n name,\n in: location,\n schema\n };\n if (required) param['required'] = true;\n if (description) param['description'] = description;\n return param;\n}\n\nfunction buildRequestBody(\n bodySchema: SchemaBuilder<any, any, any, any, any>,\n registry: SchemaRegistry,\n example?: unknown | null,\n examples?: Record<\n string,\n { summary?: string; description?: string; value: unknown }\n > | null\n): Record<string, unknown> {\n const jsonSchema = convertSchema(bodySchema, registry);\n const bodyInfo = bodySchema.introspect() as any;\n const mediaType: Record<string, unknown> = { schema: jsonSchema };\n if (example != null) {\n mediaType['example'] = example;\n } else if (examples != null) {\n mediaType['examples'] = examples;\n }\n const body: Record<string, unknown> = {\n required: bodyInfo.isRequired !== false,\n content: {\n 'application/json': mediaType\n }\n };\n if (typeof bodyInfo.description === 'string' && bodyInfo.description !== '')\n body['description'] = bodyInfo.description;\n return body;\n}\n\n// Default descriptions for common HTTP status codes\nconst HTTP_STATUS_DESCRIPTIONS: Record<number, string> = {\n 200: 'OK',\n 201: 'Created',\n 202: 'Accepted',\n 204: 'No Content',\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 422: 'Unprocessable Entity',\n 500: 'Internal Server Error'\n};\n\n// Minimal inline schema for ProblemDetails error responses\nconst PROBLEM_DETAILS_SCHEMA = {\n type: 'object',\n properties: {\n status: { type: 'integer' },\n title: { type: 'string' },\n detail: { type: 'string' }\n }\n};\n\nfunction buildResponseHeaders(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n registry: SchemaRegistry\n): Record<string, unknown> {\n const info = schema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any, any, any>\n > = info.properties ?? {};\n const headers: Record<string, unknown> = {};\n for (const [name, propSchema] of Object.entries(props)) {\n const propInfo = propSchema.introspect() as any;\n const header: Record<string, unknown> = {\n schema: convertSchema(propSchema, registry)\n };\n if (\n typeof propInfo.description === 'string' &&\n propInfo.description !== ''\n ) {\n header['description'] = propInfo.description;\n }\n headers[name] = header;\n }\n return headers;\n}\n\nfunction buildResponses(\n meta: EndpointMetadata,\n method: string,\n registry: SchemaRegistry\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n // Binary file response — takes precedence over JSON schema\n if (meta.producesFile) {\n const ct = meta.producesFile.contentType ?? 'application/octet-stream';\n const desc = meta.producesFile.description ?? 'File download';\n result['200'] = {\n description: desc,\n content: {\n [ct]: { schema: { type: 'string', format: 'binary' } }\n }\n };\n } else if (meta.responsesSchemas) {\n for (const [codeStr, schema] of Object.entries(meta.responsesSchemas)) {\n const code = Number(codeStr);\n const desc =\n HTTP_STATUS_DESCRIPTIONS[code] ?? `Response ${codeStr}`;\n if (schema) {\n const jsonSchema = convertSchema(schema, registry);\n const respInfo = schema.introspect() as any;\n const customDesc =\n typeof respInfo.description === 'string' &&\n respInfo.description !== ''\n ? respInfo.description\n : desc;\n result[codeStr] = {\n description: customDesc,\n content: { 'application/json': { schema: jsonSchema } }\n };\n } else {\n result[codeStr] = { description: desc };\n }\n }\n } else if (meta.responseSchema) {\n // Legacy single-code path — .returns() was called\n const jsonSchema = convertSchema(meta.responseSchema, registry);\n const respInfo = meta.responseSchema.introspect() as any;\n const desc =\n typeof respInfo.description === 'string' &&\n respInfo.description !== ''\n ? respInfo.description\n : 'Successful response';\n result['200'] = {\n description: desc,\n content: { 'application/json': { schema: jsonSchema } }\n };\n } else if (method === 'DELETE' || method === 'HEAD') {\n result['204'] = { description: 'No content' };\n } else {\n result['200'] = { description: 'Successful response' };\n }\n\n // Auto-add framework-generated error responses\n if (meta.bodySchema && !result['422']) {\n result['422'] = {\n description: 'Validation error',\n content: {\n 'application/problem+json': { schema: PROBLEM_DETAILS_SCHEMA }\n }\n };\n }\n\n if (meta.authRoles !== null) {\n if (!result['401']) {\n result['401'] = {\n description: 'Unauthorized',\n content: {\n 'application/problem+json': {\n schema: PROBLEM_DETAILS_SCHEMA\n }\n }\n };\n }\n if (!result['403']) {\n result['403'] = {\n description: 'Forbidden',\n content: {\n 'application/problem+json': {\n schema: PROBLEM_DETAILS_SCHEMA\n }\n }\n };\n }\n }\n\n // Multiple content types — augment each response's content map with extra\n // MIME types from .produces(). producesFile already handled above (binary wins).\n if (meta.produces && !meta.producesFile) {\n for (const code of Object.keys(result)) {\n const entry = result[code] as Record<string, unknown>;\n const existingContent = entry['content'] as\n | Record<string, unknown>\n | undefined;\n // Only augment success responses that already have a content map\n // (skip error responses whose content is application/problem+json)\n if (\n !existingContent ||\n existingContent['application/problem+json']\n ) {\n continue;\n }\n const baseSchema =\n (existingContent['application/json'] as Record<string, unknown>)\n ?.schema ?? {};\n for (const [mimeType, typeEntry] of Object.entries(meta.produces)) {\n if (mimeType === 'application/json') continue;\n existingContent[mimeType] = {\n schema: typeEntry.schema\n ? convertSchema(typeEntry.schema, registry)\n : baseSchema\n };\n }\n }\n }\n\n // Response headers — inject into every response code when declared\n if (meta.responseHeaderSchema) {\n const headers = buildResponseHeaders(\n meta.responseHeaderSchema,\n registry\n );\n for (const code of Object.keys(result)) {\n (result[code] as Record<string, unknown>)['headers'] = headers;\n }\n }\n\n // Links — attach to the lowest 2xx response\n if (meta.links) {\n const linksOut: Record<string, unknown> = {};\n for (const [linkName, linkDef] of Object.entries(meta.links)) {\n const link: Record<string, unknown> = {\n operationId: linkDef.operationId\n };\n if (linkDef.description) link['description'] = linkDef.description;\n if (linkDef.requestBody) link['requestBody'] = linkDef.requestBody;\n if (linkDef.parameters) {\n let params: Record<string, unknown>;\n if (\n typeof linkDef.parameters === 'function' &&\n meta.responseSchema\n ) {\n const tree = ObjectSchemaBuilder.getPropertiesFor(\n meta.responseSchema as ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n );\n const raw = (\n linkDef.parameters as (\n r: any\n ) => Record<string, unknown>\n )(tree);\n params = {};\n for (const [paramName, value] of Object.entries(raw)) {\n if (\n value &&\n typeof value === 'object' &&\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in\n (value as object)\n ) {\n const pointer = (value as any)[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ].toJsonPointer();\n params[paramName] = `$response.body#${pointer}`;\n } else {\n params[paramName] = value;\n }\n }\n } else {\n params = linkDef.parameters as Record<string, unknown>;\n }\n link['parameters'] = params;\n }\n linksOut[linkName] = link;\n }\n const successCode = Object.keys(result)\n .filter(k => Number(k) >= 200 && Number(k) < 300)\n .sort()[0];\n if (successCode) {\n (result[successCode] as Record<string, unknown>)['links'] =\n linksOut;\n }\n }\n\n return result;\n}\n\nfunction buildOperation(\n meta: EndpointMetadata,\n pathParams: { name: string; schema: Record<string, unknown> }[],\n securitySchemeNames: string[],\n registry: SchemaRegistry\n): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // Metadata\n if (meta.summary) operation['summary'] = meta.summary;\n if (meta.description) operation['description'] = meta.description;\n if (meta.tags.length > 0) operation['tags'] = [...meta.tags];\n if (meta.operationId) operation['operationId'] = meta.operationId;\n if (meta.deprecated) operation['deprecated'] = true;\n\n // Parameters\n const parameters: Record<string, unknown>[] = [];\n\n // Path parameters\n for (const pp of pathParams) {\n parameters.push(buildParameterObject(pp.name, 'path', pp.schema, true));\n }\n\n // Query parameters\n if (meta.querySchema) {\n const queryInfo = meta.querySchema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any>\n > = queryInfo.properties ?? {};\n for (const [name, propSchema] of Object.entries(props)) {\n const propInfo = propSchema.introspect() as any;\n const isRequired = propInfo.isRequired !== false;\n const description =\n typeof propInfo.description === 'string' &&\n propInfo.description !== ''\n ? propInfo.description\n : undefined;\n parameters.push(\n buildParameterObject(\n name,\n 'query',\n convertSchema(propSchema, registry),\n isRequired,\n description\n )\n );\n }\n }\n\n // Header parameters\n if (meta.headerSchema) {\n const headerInfo = meta.headerSchema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any>\n > = headerInfo.properties ?? {};\n for (const [name, propSchema] of Object.entries(props)) {\n const propInfo = propSchema.introspect() as any;\n const isRequired = propInfo.isRequired !== false;\n const description =\n typeof propInfo.description === 'string' &&\n propInfo.description !== ''\n ? propInfo.description\n : undefined;\n parameters.push(\n buildParameterObject(\n name,\n 'header',\n convertSchema(propSchema, registry),\n isRequired,\n description\n )\n );\n }\n }\n\n if (parameters.length > 0) operation['parameters'] = parameters;\n\n // Request body\n if (meta.bodySchema) {\n operation['requestBody'] = buildRequestBody(\n meta.bodySchema,\n registry,\n meta.example,\n meta.examples\n );\n }\n\n // Responses\n operation['responses'] = buildResponses(\n meta,\n meta.method.toUpperCase(),\n registry\n );\n\n // Security\n const security = mapOperationSecurity(authRoles(meta), securitySchemeNames);\n if (security.length > 0) operation['security'] = security;\n\n // External documentation\n if (meta.externalDocs) {\n const ed: Record<string, unknown> = { url: meta.externalDocs.url };\n if (meta.externalDocs.description)\n ed['description'] = meta.externalDocs.description;\n operation['externalDocs'] = ed;\n }\n\n // Callbacks\n if (meta.callbacks) {\n const callbacksOut: Record<string, unknown> = {};\n for (const [cbName, cbDef] of Object.entries(meta.callbacks)) {\n // Determine URL expression\n let expression: string;\n if (cbDef.expression) {\n expression = cbDef.expression;\n } else if (cbDef.urlFrom && meta.bodySchema) {\n const tree = ObjectSchemaBuilder.getPropertiesFor(\n meta.bodySchema as ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n );\n const selected = cbDef.urlFrom(tree);\n if (\n selected &&\n typeof selected === 'object' &&\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in (selected as object)\n ) {\n const pointer = (selected as any)[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ].toJsonPointer();\n expression = `{$request.body#${pointer}}`;\n } else {\n expression = String(\n selected ?? '{$request.body#/callbackUrl}'\n );\n }\n } else {\n expression = '{$request.body#/callbackUrl}';\n }\n\n const cbMethod = (cbDef.method ?? 'post').toLowerCase();\n const cbOperation: Record<string, unknown> = {};\n if (cbDef.summary) cbOperation['summary'] = cbDef.summary;\n if (cbDef.description)\n cbOperation['description'] = cbDef.description;\n if (cbDef.body) {\n cbOperation['requestBody'] = buildRequestBody(\n cbDef.body,\n registry\n );\n }\n const cbResponses: Record<string, unknown> = {};\n if (cbDef.response) {\n cbResponses['200'] = {\n description: 'OK',\n content: {\n 'application/json': {\n schema: convertSchema(cbDef.response, registry)\n }\n }\n };\n } else {\n cbResponses['200'] = { description: 'OK' };\n }\n cbOperation['responses'] = cbResponses;\n callbacksOut[cbName] = {\n [expression]: { [cbMethod]: cbOperation }\n };\n }\n operation['callbacks'] = callbacksOut;\n }\n\n return operation;\n}\n\nfunction authRoles(meta: EndpointMetadata): readonly string[] | null {\n return meta.authRoles;\n}\n\n/**\n * Generate an OpenAPI 3.1 specification document from registered endpoints.\n */\nexport function generateOpenApiSpec(options: OpenApiOptions): OpenApiDocument {\n const { registrations, info, servers, authConfig, securitySchemes, tags } =\n options;\n\n // Security schemes — from explicit config or auto-mapped\n const resolvedSchemes: Record<string, OpenApiSecurityScheme> =\n securitySchemes ?? mapSecuritySchemes(authConfig);\n const securitySchemeNames = Object.keys(resolvedSchemes);\n\n // Pre-pass: collect all named schemas from every endpoint into a registry.\n // Walking happens before path generation so that $ref pointers are emitted\n // correctly at every call site within buildOperation.\n const registry = new SchemaRegistry();\n const visited = new Set<SchemaBuilder<any, any, any>>();\n for (const reg of registrations) {\n const meta = reg.endpoint;\n if (meta.bodySchema) walkSchemas(meta.bodySchema, registry, visited);\n if (meta.responseSchema)\n walkSchemas(meta.responseSchema, registry, visited);\n if (meta.responsesSchemas) {\n for (const schema of Object.values(meta.responsesSchemas)) {\n if (schema) walkSchemas(schema, registry, visited);\n }\n }\n if (meta.querySchema) {\n const queryProps =\n (meta.querySchema.introspect() as any).properties ?? {};\n for (const propSchema of Object.values<\n SchemaBuilder<any, any, any>\n >(queryProps)) {\n walkSchemas(propSchema, registry, visited);\n }\n }\n if (meta.headerSchema) {\n const headerProps =\n (meta.headerSchema.introspect() as any).properties ?? {};\n for (const propSchema of Object.values<\n SchemaBuilder<any, any, any>\n >(headerProps)) {\n walkSchemas(propSchema, registry, visited);\n }\n }\n if (meta.callbacks) {\n for (const cbDef of Object.values(meta.callbacks)) {\n if (cbDef.body) walkSchemas(cbDef.body, registry, visited);\n if (cbDef.response)\n walkSchemas(cbDef.response, registry, visited);\n }\n }\n }\n if (options.webhooks) {\n for (const webhook of options.webhooks) {\n if (webhook.body) walkSchemas(webhook.body, registry, visited);\n if (webhook.response)\n walkSchemas(webhook.response, registry, visited);\n }\n }\n\n const resolveComponentSchemaName = (\n rootSchema: SchemaBuilder<any, any, any>\n ) => {\n // The root schema must be inlined exactly once — for the component\n // definition itself. Any subsequent encounter (e.g. through a lazy\n // self-reference) should emit a $ref instead of inlining again,\n // which would otherwise cause infinite recursion.\n let inlinedRoot = false;\n return (\n candidate: SchemaBuilder<any, any, any>\n ): string | undefined => {\n if (candidate === rootSchema && !inlinedRoot) {\n inlinedRoot = true;\n return undefined; // inline the root definition once\n }\n return registry.getName(candidate) ?? undefined;\n };\n };\n\n // Build paths\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const reg of registrations) {\n const meta = reg.endpoint;\n const { path, parameters: pathParams } = resolvePath(meta);\n const method = meta.method.toLowerCase();\n\n if (!paths[path]) paths[path] = {};\n paths[path][method] = buildOperation(\n meta,\n pathParams as { name: string; schema: Record<string, unknown> }[],\n securitySchemeNames,\n registry\n );\n }\n\n // Build top-level tags array: explicit entries first, then auto-collected\n // tag names from endpoints that are not already covered.\n const explicitNames = new Set((tags ?? []).map(t => t.name));\n const autoNames: string[] = [];\n for (const reg of registrations) {\n for (const tag of reg.endpoint.tags) {\n if (!explicitNames.has(tag) && !autoNames.includes(tag)) {\n autoNames.push(tag);\n }\n }\n }\n autoNames.sort();\n const mergedTags: OpenApiTag[] = [\n ...(tags ?? []),\n ...autoNames.map(name => ({ name }))\n ];\n\n // Assemble document\n const doc: OpenApiDocument = {\n openapi: '3.1.0',\n info: { ...info }\n };\n\n if (servers && servers.length > 0) {\n doc['servers'] = servers.map(s => ({ ...s }));\n }\n\n if (mergedTags.length > 0) {\n doc['tags'] = mergedTags.map(t => ({ ...t }));\n }\n\n doc['paths'] = paths;\n\n // Webhooks — out-of-band async requests documented alongside paths\n if (options.webhooks && options.webhooks.length > 0) {\n const webhooksObj: Record<string, unknown> = {};\n for (const webhook of options.webhooks) {\n const whMethod = (webhook.method ?? 'post').toLowerCase();\n const whOperation: Record<string, unknown> = {};\n if (webhook.summary) whOperation['summary'] = webhook.summary;\n if (webhook.description)\n whOperation['description'] = webhook.description;\n if (webhook.tags && webhook.tags.length > 0)\n whOperation['tags'] = [...webhook.tags];\n if (webhook.body) {\n whOperation['requestBody'] = buildRequestBody(\n webhook.body,\n registry\n );\n }\n const whResponses: Record<string, unknown> = {};\n if (webhook.response) {\n whResponses['200'] = {\n description: 'OK',\n content: {\n 'application/json': {\n schema: convertSchema(webhook.response, registry)\n }\n }\n };\n } else {\n whResponses['200'] = { description: 'OK' };\n }\n whOperation['responses'] = whResponses;\n webhooksObj[webhook.name] = { [whMethod]: whOperation };\n }\n doc['webhooks'] = webhooksObj;\n }\n\n // Components — security schemes + named component schemas\n const componentSchemas: Record<string, unknown> = {};\n for (const [name, schema] of registry.entries()) {\n // Inline the root schema to avoid a self-referential $ref, but resolve\n // nested named schemas through the shared registry so component\n // definitions can still deduplicate via $ref.\n componentSchemas[name] = convertSchema(\n schema,\n resolveComponentSchemaName(schema)\n );\n }\n const hasSchemas = Object.keys(componentSchemas).length > 0;\n\n if (securitySchemeNames.length > 0 || hasSchemas) {\n const components: Record<string, unknown> = {};\n if (securitySchemeNames.length > 0)\n components['securitySchemes'] = { ...resolvedSchemes };\n if (hasSchemas) components['schemas'] = componentSchemas;\n doc['components'] = components;\n }\n\n return doc;\n}\n","import type {\n ParseStringSchemaBuilder,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport { toJsonSchema } from '@cleverbrush/schema-json';\nimport type { EndpointMetadata } from '@cleverbrush/server';\n\ntype RoutePath = string | ParseStringSchemaBuilder<any, any, any, any, any>;\n\nfunction isParseStringSchema(\n p: RoutePath\n): p is ParseStringSchemaBuilder<any, any, any, any, any> {\n return typeof p !== 'string' && typeof (p as any).validate === 'function';\n}\n\n/**\n * Describes a single path parameter extracted from a route template,\n * including its name and the JSON Schema representation of its type.\n */\nexport interface PathParameterInfo {\n readonly name: string;\n readonly schema: Record<string, unknown>;\n}\n\n/**\n * The output of resolving a route path template to an OpenAPI-compatible\n * path string and its parameter list.\n */\nexport interface ResolvedPath {\n /** OpenAPI-formatted path, e.g. `/api/users/{id}` */\n readonly path: string;\n /** Extracted path parameters with their JSON Schema */\n readonly parameters: readonly PathParameterInfo[];\n}\n\n/**\n * Convert a colon-style static path to OpenAPI `{param}` format.\n * E.g. `/users/:id/posts/:pid` → `/users/{id}/posts/{pid}`\n */\nfunction convertColonParams(path: string): {\n converted: string;\n paramNames: string[];\n} {\n const paramNames: string[] = [];\n const converted = path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {\n paramNames.push(name);\n return `{${name}}`;\n });\n return { converted, paramNames };\n}\n\n/**\n * Convert a `ParseStringSchemaBuilder` path template to an OpenAPI-formatted\n * path string and extract parameter schemas.\n */\nfunction convertParseStringPath(\n pathTemplate: ParseStringSchemaBuilder<any, any, any, any, any>\n): { pathString: string; parameters: PathParameterInfo[] } {\n const info = pathTemplate.introspect() as any;\n const templateDef = info.templateDefinition as {\n literals: string[];\n segments: { path: string }[];\n };\n const objectSchema = info.objectSchema;\n const objInfo = objectSchema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any>\n > = objInfo.properties ?? {};\n\n let pathString = '';\n for (let i = 0; i < templateDef.segments.length; i++) {\n pathString +=\n templateDef.literals[i] + `{${templateDef.segments[i].path}}`;\n }\n pathString += templateDef.literals[templateDef.segments.length] ?? '';\n\n const parameters: PathParameterInfo[] = templateDef.segments.map(seg => ({\n name: seg.path,\n schema: props[seg.path]\n ? toJsonSchema(props[seg.path], { $schema: false })\n : { type: 'string' }\n }));\n\n return { pathString, parameters };\n}\n\n/**\n * Combines `basePath` and `pathTemplate` from an endpoint into an\n * OpenAPI-formatted path with extracted parameter information.\n */\nexport function resolvePath(meta: EndpointMetadata): ResolvedPath {\n const basePath = meta.basePath.replace(/\\/$/, '');\n const pathTemplate = meta.pathTemplate;\n\n if (isParseStringSchema(pathTemplate)) {\n const { pathString, parameters } = convertParseStringPath(pathTemplate);\n const fullPath = normalizeSlashes(basePath + pathString);\n return { path: fullPath, parameters };\n }\n\n // Static string path template\n const templateStr = pathTemplate === '/' ? '' : pathTemplate;\n const combined = basePath + templateStr;\n const { converted, paramNames } = convertColonParams(combined);\n const fullPath = normalizeSlashes(converted);\n\n const parameters: PathParameterInfo[] = paramNames.map(name => ({\n name,\n schema: { type: 'string' }\n }));\n\n return { path: fullPath, parameters };\n}\n\nfunction normalizeSlashes(path: string): string {\n // Replace double slashes with single, ensure leading slash\n let result = path.replace(/\\/+/g, '/');\n if (!result.startsWith('/')) result = '/' + result;\n if (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1);\n return result || '/';\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport { toJsonSchema } from '@cleverbrush/schema-json';\nimport type { SchemaRegistry } from './schemaRegistry.js';\n\ntype NameResolver = (\n schema: SchemaBuilder<any, any, any>\n) => string | null | undefined;\n\n/**\n * Converts a `@cleverbrush/schema` builder to a JSON Schema object suitable\n * for embedding in an OpenAPI 3.1 spec (no `$schema` header, Draft 2020-12).\n *\n * Returns an empty schema `{}` when the input is `null` or `undefined`.\n *\n * When a {@link SchemaRegistry} or a custom resolver function is provided, any\n * schema instance that resolves to a name will be emitted as a\n * `$ref: '#/components/schemas/<name>'` pointer instead of being inlined.\n *\n * @param schema - The schema to convert, or `null`/`undefined`.\n * @param registry - Optional registry or resolver function for `$ref` deduplication.\n */\nexport function convertSchema(\n schema: SchemaBuilder<any, any, any, any, any> | null | undefined,\n registry?: SchemaRegistry | NameResolver\n): Record<string, unknown> {\n if (schema == null) return {};\n\n let nameResolver:\n | ((s: SchemaBuilder<any, any, any>) => string | null)\n | undefined;\n if (registry) {\n if (typeof registry === 'function') {\n nameResolver = s => registry(s) ?? null;\n } else {\n nameResolver = s => registry.getName(s);\n }\n }\n\n return toJsonSchema(schema, {\n $schema: false,\n draft: '2020-12',\n nameResolver\n });\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\n\n// ---------------------------------------------------------------------------\n// SchemaRegistry\n// ---------------------------------------------------------------------------\n\n/**\n * Collects schemas that carry an explicit component name (set via\n * `.schemaName()`) and provides a reference-based lookup used during OpenAPI\n * spec generation to replace inline schema objects with\n * `$ref: '#/components/schemas/<name>'` pointers.\n *\n * **Conflict rule**: registering two *different* schema instances (different\n * object references) under the same name throws immediately. Re-registering\n * the same instance is a no-op.\n *\n * @example\n * ```ts\n * const registry = new SchemaRegistry();\n * registry.register(UserSchema); // UserSchema.schemaName('User')\n * registry.getName(UserSchema); // 'User'\n * registry.getName(someOtherSchema); // null\n * ```\n */\nexport class SchemaRegistry {\n /** schema instance → registered name */\n private readonly byInstance = new Map<\n SchemaBuilder<any, any, any>,\n string\n >();\n /** name → first-registered schema instance */\n private readonly byName = new Map<string, SchemaBuilder<any, any, any>>();\n\n /**\n * Attempts to register `schema` in the registry.\n *\n * - If the schema has no `schemaName` in its introspect output, it is\n * silently skipped.\n * - If the same instance is already registered, this is a no-op.\n * - If a **different** instance is already registered under the same name,\n * an error is thrown.\n *\n * @param schema - The schema builder to register.\n * @throws {Error} When two distinct schema instances share the same name.\n */\n register(schema: SchemaBuilder<any, any, any>): void {\n const name = (schema.introspect() as any).schemaName as\n | string\n | undefined;\n if (typeof name !== 'string') return;\n\n const existing = this.byName.get(name);\n if (existing !== undefined) {\n // Same instance → idempotent, nothing to do\n if (existing === schema) return;\n // Different instance → conflict\n throw new Error(\n `Schema name \"${name}\" is already registered by a different schema instance. ` +\n `Each named schema must be a single, reused constant. ` +\n `If you intended to register the same schema, ensure you are passing ` +\n `the same object reference (not a rebuilt schema).`\n );\n }\n\n this.byInstance.set(schema, name);\n this.byName.set(name, schema);\n }\n\n /**\n * Returns the component name for a given schema instance, or `null` if it\n * was not registered.\n *\n * @param schema - The schema builder to look up.\n * @returns The registered name, or `null`.\n */\n getName(schema: SchemaBuilder<any, any, any>): string | null {\n return this.byInstance.get(schema) ?? null;\n }\n\n /**\n * Iterates over all registered `[name, schema]` pairs in insertion order.\n *\n * Used to emit the `components.schemas` section of an OpenAPI document.\n */\n entries(): IterableIterator<[string, SchemaBuilder<any, any, any>]> {\n return this.byName.entries();\n }\n\n /** Returns `true` when at least one schema has been registered. */\n get isEmpty(): boolean {\n return this.byName.size === 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// walkSchemas\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively visits every {@link SchemaBuilder} reachable from `schema` and\n * calls {@link SchemaRegistry.register} on each node.\n *\n * Cycle detection is performed via a `visited` `Set` of object references, so\n * schemas may safely be shared across multiple branches without causing\n * infinite recursion.\n *\n * **Excluded schema types**\n * - `lazy` — deferred resolution would require calling the getter, which may\n * itself reference the parent schema; lazy schemas are handled separately.\n *\n * @param schema - Root schema to start the walk from.\n * @param registry - Registry to register named schemas into.\n * @param visited - Shared set for cycle detection; pass a new `Set()` for the\n * top-level call.\n */\nexport function walkSchemas(\n schema: SchemaBuilder<any, any, any>,\n registry: SchemaRegistry,\n visited: Set<SchemaBuilder<any, any, any>> = new Set()\n): void {\n if (visited.has(schema)) return;\n visited.add(schema);\n\n registry.register(schema);\n\n const info = schema.introspect() as any;\n\n switch (info.type) {\n case 'object': {\n const props = info.properties as\n | Record<string, SchemaBuilder<any, any, any>>\n | undefined;\n if (props) {\n for (const child of Object.values(props)) {\n walkSchemas(child, registry, visited);\n }\n }\n break;\n }\n case 'array':\n if (info.elementSchema) {\n walkSchemas(info.elementSchema, registry, visited);\n }\n break;\n case 'tuple': {\n const elements: SchemaBuilder<any, any, any>[] =\n info.elements ?? [];\n for (const el of elements) {\n walkSchemas(el, registry, visited);\n }\n if (info.restSchema) {\n walkSchemas(info.restSchema, registry, visited);\n }\n break;\n }\n case 'union': {\n const options: SchemaBuilder<any, any, any>[] = info.options ?? [];\n for (const opt of options) {\n walkSchemas(opt, registry, visited);\n }\n break;\n }\n case 'record':\n if (info.keySchema) {\n walkSchemas(info.keySchema, registry, visited);\n }\n if (info.valueSchema) {\n walkSchemas(info.valueSchema, registry, visited);\n }\n break;\n case 'lazy': {\n // Resolve the inner schema and walk it so that any named schema\n // reachable through a lazy boundary is registered. Cycle-safety is\n // provided by the `visited` set — the lazy wrapper itself was\n // already added above, preventing infinite recursion on\n // self-referential schemas.\n const resolved = (schema as any).resolve() as SchemaBuilder<\n any,\n any,\n any\n >;\n walkSchemas(resolved, registry, visited);\n break;\n }\n default:\n break;\n }\n}\n","import type { AuthenticationConfig } from '@cleverbrush/server';\n\n/**\n * An OpenAPI 3.1 Security Scheme Object.\n * Describes how requests to the API are authenticated.\n *\n * @see {@link https://spec.openapis.org/oas/v3.1.0#security-scheme-object OpenAPI Security Scheme Object}\n */\nexport interface OpenApiSecurityScheme {\n readonly type: string;\n readonly scheme?: string;\n readonly bearerFormat?: string;\n readonly in?: string;\n readonly name?: string;\n readonly flows?: Record<string, unknown>;\n readonly openIdConnectUrl?: string;\n}\n\n/**\n * Map `@cleverbrush/auth` authentication schemes to OpenAPI security scheme objects.\n */\nexport function mapSecuritySchemes(\n authConfig: AuthenticationConfig | null | undefined\n): Record<string, OpenApiSecurityScheme> {\n if (!authConfig) return {};\n\n const result: Record<string, OpenApiSecurityScheme> = {};\n for (const scheme of authConfig.schemes) {\n const name = scheme.name;\n const challenge = scheme.challenge?.();\n\n if ('openIdConnectUrl' in scheme && (scheme as any).openIdConnectUrl) {\n result[name] = {\n type: 'openIdConnect',\n openIdConnectUrl: (scheme as any).openIdConnectUrl\n };\n } else if ('flows' in scheme && (scheme as any).flows) {\n result[name] = {\n type: 'oauth2',\n flows: (scheme as any).flows\n };\n } else if (\n challenge?.headerValue?.toLowerCase().startsWith('bearer') ||\n name === 'jwt'\n ) {\n result[name] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT'\n };\n } else if (name === 'cookie' || (scheme as any).cookieName) {\n // Cookie scheme — extract cookie name from options if available\n const cookieName =\n (scheme as any).cookieName ??\n (scheme as any)._options?.cookieName ??\n 'session';\n result[name] = {\n type: 'apiKey',\n in: 'cookie',\n name: cookieName\n };\n } else if (challenge) {\n // Generic scheme with a challenge header\n result[name] = {\n type: 'http',\n scheme: challenge.headerValue.split(' ')[0].toLowerCase()\n };\n } else {\n // Fallback — treat as generic HTTP scheme\n result[name] = { type: 'http', scheme: name };\n }\n }\n return result;\n}\n\n/**\n * Map endpoint `authRoles` to an OpenAPI operation-level `security` array.\n *\n * - `null` → empty array (public endpoint, no security)\n * - `[]` → `[{ <schemeName>: [] }]` (any authenticated user)\n * - `['admin']` → `[{ <schemeName>: ['admin'] }]` (require specific roles)\n */\nexport function mapOperationSecurity(\n authRoles: readonly string[] | null,\n securitySchemeNames: string[]\n): Record<string, string[]>[] {\n if (authRoles === null) return [];\n if (securitySchemeNames.length === 0) return [];\n\n // Each security scheme listed as an option (OR semantics in OpenAPI)\n return securitySchemeNames.map(name => ({\n [name]: [...authRoles]\n }));\n}\n","import type {\n AuthenticationConfig,\n EndpointRegistration\n} from '@cleverbrush/server';\nimport { endpoint } from '@cleverbrush/server';\nimport {\n generateOpenApiSpec,\n type OpenApiDocument,\n type OpenApiInfo,\n type OpenApiServer\n} from './generateOpenApiSpec.js';\nimport type { OpenApiSecurityScheme } from './securityMapper.js';\n\n/**\n * Options for {@link createOpenApiEndpoint}.\n */\nexport interface OpenApiEndpointOptions {\n /** Function that returns endpoint registrations. */\n readonly getRegistrations: () => readonly EndpointRegistration[];\n /** OpenAPI info metadata. */\n readonly info: OpenApiInfo;\n /** Optional server entries. */\n readonly servers?: readonly OpenApiServer[];\n /** Optional auth config for security scheme generation. */\n readonly authConfig?: AuthenticationConfig | null;\n /** Override security schemes manually. */\n readonly securitySchemes?: Record<string, OpenApiSecurityScheme>;\n /** Path to serve the spec at (default: `/openapi.json`). */\n readonly path?: string;\n}\n\n/**\n * Creates an endpoint definition and handler that serves the OpenAPI spec\n * as JSON. Register it with `builder.handle(ep, handler)`.\n *\n * The spec is lazily generated on first request and cached.\n *\n * @example\n * ```ts\n * const { endpoint: openApiEp, handler } = createOpenApiEndpoint({\n * getRegistrations: () => builder.getRegistrations(),\n * info: { title: 'My API', version: '1.0.0' }\n * });\n * builder.handle(openApiEp, handler);\n * ```\n */\nexport function createOpenApiEndpoint(options: OpenApiEndpointOptions): {\n endpoint: ReturnType<(typeof endpoint)['get']>;\n handler: () => OpenApiDocument;\n} {\n const servePath = options.path ?? '/openapi.json';\n\n const ep = endpoint\n .get(servePath)\n .summary('OpenAPI specification')\n .tags('OpenAPI')\n .operationId('getOpenApiSpec');\n\n let cachedSpec: OpenApiDocument | null = null;\n\n const handler = (): OpenApiDocument => {\n if (!cachedSpec) {\n cachedSpec = generateOpenApiSpec({\n registrations: options.getRegistrations(),\n info: options.info,\n servers: options.servers,\n authConfig: options.authConfig,\n securitySchemes: options.securitySchemes\n });\n }\n return cachedSpec;\n };\n\n return { endpoint: ep, handler };\n}\n","import type {\n AuthenticationConfig,\n EndpointRegistration,\n RequestContext\n} from '@cleverbrush/server';\nimport {\n generateOpenApiSpec,\n type OpenApiDocument,\n type OpenApiInfo,\n type OpenApiServer\n} from './generateOpenApiSpec.js';\nimport type { OpenApiSecurityScheme } from './securityMapper.js';\n\n/**\n * Options for the {@link serveOpenApi} middleware.\n */\nexport interface ServeOpenApiOptions {\n /** Function that returns endpoint registrations. */\n readonly getRegistrations: () => readonly EndpointRegistration[];\n /** OpenAPI info metadata. */\n readonly info: OpenApiInfo;\n /** Optional server entries. */\n readonly servers?: readonly OpenApiServer[];\n /** Optional auth config for security scheme generation. */\n readonly authConfig?: AuthenticationConfig | null;\n /** Override security schemes manually. */\n readonly securitySchemes?: Record<string, OpenApiSecurityScheme>;\n /** Path to serve the spec at (default: `/openapi.json`). */\n readonly path?: string;\n}\n\n/**\n * Returns a server middleware that serves the OpenAPI spec as JSON at\n * the configured path (default: `/openapi.json`).\n *\n * The spec is lazily generated on first request and cached.\n */\nexport function serveOpenApi(\n options: ServeOpenApiOptions\n): (context: RequestContext, next: () => Promise<void>) => Promise<void> {\n const servePath = options.path ?? '/openapi.json';\n let cachedSpec: OpenApiDocument | null = null;\n\n return async (context, next) => {\n const url = context.url;\n const pathname = url.pathname;\n const method = context.method.toUpperCase();\n\n if (method === 'GET' && pathname === servePath) {\n if (!cachedSpec) {\n cachedSpec = generateOpenApiSpec({\n registrations: options.getRegistrations(),\n info: options.info,\n servers: options.servers,\n authConfig: options.authConfig,\n securitySchemes: options.securitySchemes\n });\n }\n const body = JSON.stringify(cachedSpec);\n context.response.writeHead(200, {\n 'content-type': 'application/json',\n 'content-length': Buffer.byteLength(body).toString()\n });\n context.response.end(body);\n return;\n }\n\n await next();\n };\n}\n"],"mappings":"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","names":["fs","path","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","toJsonSchema","isParseStringSchema","p","convertColonParams","path","paramNames","_","name","convertParseStringPath","pathTemplate","info","templateDef","props","pathString","i","parameters","seg","resolvePath","meta","basePath","normalizeSlashes","combined","converted","fullPath","result","toJsonSchema","convertSchema","schema","registry","nameResolver","s","SchemaRegistry","schema","name","existing","walkSchemas","registry","visited","info","props","child","elements","el","options","opt","resolved","mapSecuritySchemes","authConfig","result","scheme","name","challenge","cookieName","mapOperationSecurity","authRoles","securitySchemeNames","buildParameterObject","name","location","schema","required","description","param","buildRequestBody","bodySchema","registry","example","examples","jsonSchema","convertSchema","bodyInfo","mediaType","body","HTTP_STATUS_DESCRIPTIONS","PROBLEM_DETAILS_SCHEMA","buildResponseHeaders","props","headers","propSchema","propInfo","header","buildResponses","meta","method","result","ct","desc","codeStr","code","respInfo","customDesc","existingContent","baseSchema","mimeType","typeEntry","linksOut","linkName","linkDef","link","params","tree","ObjectSchemaBuilder","raw","paramName","value","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","pointer","successCode","k","buildOperation","pathParams","securitySchemeNames","operation","parameters","pp","isRequired","security","mapOperationSecurity","authRoles","ed","callbacksOut","cbName","cbDef","expression","selected","cbMethod","cbOperation","cbResponses","generateOpenApiSpec","options","registrations","info","servers","authConfig","securitySchemes","tags","resolvedSchemes","mapSecuritySchemes","SchemaRegistry","visited","reg","walkSchemas","queryProps","headerProps","webhook","resolveComponentSchemaName","rootSchema","inlinedRoot","candidate","paths","path","resolvePath","explicitNames","t","autoNames","tag","mergedTags","doc","s","webhooksObj","whMethod","whOperation","whResponses","componentSchemas","hasSchemas","components","writeOpenApiSpec","options","outputPath","spec","generateOpenApiSpec","dir","endpoint","createOpenApiEndpoint","options","servePath","ep","endpoint","cachedSpec","generateOpenApiSpec","serveOpenApi","options","servePath","cachedSpec","context","next","pathname","generateOpenApiSpec","body"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/generateOpenApiSpec.ts","../src/pathUtils.ts","../src/schemaConverter.ts","../src/schemaRegistry.ts","../src/securityMapper.ts","../src/openApiEndpoint.ts","../src/serveOpenApi.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport {\n generateOpenApiSpec,\n type OpenApiDocument,\n type OpenApiOptions\n} from './generateOpenApiSpec.js';\n\n/**\n * Generate an OpenAPI spec and write it to a file.\n *\n * @param options - Same options as `generateOpenApiSpec()`.\n * @param outputPath - File path to write the JSON spec to.\n * @returns The generated spec document.\n *\n * @example\n * ```ts\n * import { writeOpenApiSpec } from '@cleverbrush/server-openapi';\n * import { createServer, endpoint } from '@cleverbrush/server';\n *\n * const builder = createServer()\n * .handle(endpoint.get('/api/health'), () => ({ ok: true }));\n *\n * writeOpenApiSpec(\n * {\n * registrations: builder.getRegistrations(),\n * info: { title: 'My API', version: '1.0.0' }\n * },\n * './openapi.json'\n * );\n * ```\n */\nexport function writeOpenApiSpec(\n options: OpenApiOptions,\n outputPath: string\n): OpenApiDocument {\n const spec = generateOpenApiSpec(options);\n const dir = path.dirname(outputPath);\n if (dir && !fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2), 'utf-8');\n return spec;\n}\n","import {\n ObjectSchemaBuilder,\n type SchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport type {\n AuthenticationConfig,\n EndpointMetadata,\n EndpointRegistration,\n WebhookDefinition\n} from '@cleverbrush/server';\nimport { resolvePath } from './pathUtils.js';\nimport { convertSchema } from './schemaConverter.js';\nimport { SchemaRegistry, walkSchemas } from './schemaRegistry.js';\nimport {\n mapOperationSecurity,\n mapSecuritySchemes,\n type OpenApiSecurityScheme\n} from './securityMapper.js';\n\n// ---------------------------------------------------------------------------\n// Server interface (structural — avoids hard dependency on ServerBuilder class)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal interface for a `@cleverbrush/server` server instance. Matches\n * the relevant subset of `ServerBuilder` so that callers can pass the\n * server directly without importing the class.\n */\nexport interface OpenApiServer_ServerLike {\n getRegistrations(): readonly EndpointRegistration[];\n getAuthenticationConfig(): AuthenticationConfig | null;\n getWebhooks(): readonly WebhookDefinition[];\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * API metadata included in the OpenAPI `info` object.\n * Maps directly to the OpenAPI 3.1 Info Object.\n */\nexport interface OpenApiInfo {\n readonly title: string;\n readonly version: string;\n readonly description?: string;\n readonly termsOfService?: string;\n readonly contact?: {\n readonly name?: string;\n readonly url?: string;\n readonly email?: string;\n };\n readonly license?: {\n readonly name: string;\n readonly url?: string;\n };\n}\n\n/**\n * A server entry in the OpenAPI `servers` array.\n * Describes a base URL where the API is accessible.\n */\nexport interface OpenApiServer {\n readonly url: string;\n readonly description?: string;\n}\n\n/**\n * A tag entry in the OpenAPI top-level `tags` array.\n * Provides a name, optional description, and optional external documentation\n * for a tag group.\n *\n * @see https://spec.openapis.org/oas/v3.1.0#tag-object\n */\nexport interface OpenApiTag {\n /** Tag name. Must match the tag strings used on individual operations. */\n readonly name: string;\n /** Short description for the tag group, displayed in Swagger UI / Redoc. */\n readonly description?: string;\n /** Link to external documentation for this tag. */\n readonly externalDocs?: {\n readonly url: string;\n readonly description?: string;\n };\n}\n\n/**\n * Options passed to {@link generateOpenApiSpec}.\n *\n * When `server` is provided, `registrations` and `authConfig` are derived\n * from it automatically (unless explicitly overridden).\n */\nexport interface OpenApiOptions {\n /**\n * A `ServerBuilder` (or any object implementing the same methods).\n * When set, `registrations`, `authConfig`, and `webhooks` are\n * automatically read from the server instance. Explicit values for\n * those fields take precedence over the server-derived ones.\n */\n readonly server?: OpenApiServer_ServerLike;\n readonly registrations?: readonly EndpointRegistration[];\n readonly info: OpenApiInfo;\n readonly servers?: readonly OpenApiServer[];\n readonly authConfig?: AuthenticationConfig | null;\n readonly securitySchemes?: Record<string, OpenApiSecurityScheme>;\n /**\n * Top-level tag definitions with optional descriptions and external docs.\n *\n * When provided, these entries are emitted as the top-level `tags` array.\n * Any tag names used by registered endpoints but absent from this list are\n * automatically appended as name-only entries (sorted alphabetically).\n *\n * When omitted, unique tag names are still auto-collected from all\n * registered endpoints and emitted as name-only entries.\n *\n * @see https://spec.openapis.org/oas/v3.1.0#tag-object\n */\n readonly tags?: readonly OpenApiTag[];\n /**\n * Webhook definitions to emit in the top-level `webhooks` map of the\n * generated OpenAPI document.\n *\n * Webhooks are not served as HTTP routes — they merely document async\n * out-of-band requests that your API sends to subscribers.\n *\n * @see https://spec.openapis.org/oas/v3.1.0#fixed-fields\n */\n readonly webhooks?: readonly WebhookDefinition[];\n}\n\n// ---------------------------------------------------------------------------\n// OpenAPI Document (partial typing — plain objects for flexibility)\n// ---------------------------------------------------------------------------\n\n/**\n * A generated OpenAPI 3.1 document. Typed as a plain object map to allow\n * any extension fields without requiring a full OpenAPI type library.\n */\nexport type OpenApiDocument = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Generator\n// ---------------------------------------------------------------------------\n\nfunction buildParameterObject(\n name: string,\n location: 'query' | 'header' | 'path',\n schema: Record<string, unknown>,\n required: boolean,\n description?: string\n): Record<string, unknown> {\n const param: Record<string, unknown> = {\n name,\n in: location,\n schema\n };\n if (required) param['required'] = true;\n if (description) param['description'] = description;\n return param;\n}\n\nfunction buildRequestBody(\n bodySchema: SchemaBuilder<any, any, any, any, any>,\n registry: SchemaRegistry,\n example?: unknown | null,\n examples?: Record<\n string,\n { summary?: string; description?: string; value: unknown }\n > | null\n): Record<string, unknown> {\n const jsonSchema = convertSchema(bodySchema, registry);\n const bodyInfo = bodySchema.introspect() as any;\n const mediaType: Record<string, unknown> = { schema: jsonSchema };\n if (example != null) {\n mediaType['example'] = example;\n } else if (examples != null) {\n mediaType['examples'] = examples;\n }\n const body: Record<string, unknown> = {\n required: bodyInfo.isRequired !== false,\n content: {\n 'application/json': mediaType\n }\n };\n if (typeof bodyInfo.description === 'string' && bodyInfo.description !== '')\n body['description'] = bodyInfo.description;\n return body;\n}\n\n// Default descriptions for common HTTP status codes\nconst HTTP_STATUS_DESCRIPTIONS: Record<number, string> = {\n 200: 'OK',\n 201: 'Created',\n 202: 'Accepted',\n 204: 'No Content',\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 422: 'Unprocessable Entity',\n 500: 'Internal Server Error'\n};\n\n// Minimal inline schema for ProblemDetails error responses\nconst PROBLEM_DETAILS_SCHEMA = {\n type: 'object',\n properties: {\n status: { type: 'integer' },\n title: { type: 'string' },\n detail: { type: 'string' }\n }\n};\n\nfunction buildResponseHeaders(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n registry: SchemaRegistry\n): Record<string, unknown> {\n const info = schema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any, any, any>\n > = info.properties ?? {};\n const headers: Record<string, unknown> = {};\n for (const [name, propSchema] of Object.entries(props)) {\n const propInfo = propSchema.introspect() as any;\n const header: Record<string, unknown> = {\n schema: convertSchema(propSchema, registry)\n };\n if (\n typeof propInfo.description === 'string' &&\n propInfo.description !== ''\n ) {\n header['description'] = propInfo.description;\n }\n headers[name] = header;\n }\n return headers;\n}\n\nfunction buildResponses(\n meta: EndpointMetadata,\n method: string,\n registry: SchemaRegistry\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n // Binary file response — takes precedence over JSON schema\n if (meta.producesFile) {\n const ct = meta.producesFile.contentType ?? 'application/octet-stream';\n const desc = meta.producesFile.description ?? 'File download';\n result['200'] = {\n description: desc,\n content: {\n [ct]: { schema: { type: 'string', format: 'binary' } }\n }\n };\n } else if (meta.responsesSchemas) {\n for (const [codeStr, schema] of Object.entries(meta.responsesSchemas)) {\n const code = Number(codeStr);\n const desc =\n HTTP_STATUS_DESCRIPTIONS[code] ?? `Response ${codeStr}`;\n if (schema) {\n const jsonSchema = convertSchema(schema, registry);\n const respInfo = schema.introspect() as any;\n const customDesc =\n typeof respInfo.description === 'string' &&\n respInfo.description !== ''\n ? respInfo.description\n : desc;\n result[codeStr] = {\n description: customDesc,\n content: { 'application/json': { schema: jsonSchema } }\n };\n } else {\n result[codeStr] = { description: desc };\n }\n }\n } else if (meta.responseSchema) {\n // Legacy single-code path — .returns() was called\n const jsonSchema = convertSchema(meta.responseSchema, registry);\n const respInfo = meta.responseSchema.introspect() as any;\n const desc =\n typeof respInfo.description === 'string' &&\n respInfo.description !== ''\n ? respInfo.description\n : 'Successful response';\n result['200'] = {\n description: desc,\n content: { 'application/json': { schema: jsonSchema } }\n };\n } else if (method === 'DELETE' || method === 'HEAD') {\n result['204'] = { description: 'No content' };\n } else {\n result['200'] = { description: 'Successful response' };\n }\n\n // Auto-add framework-generated error responses\n if (meta.bodySchema && !result['422']) {\n result['422'] = {\n description: 'Validation error',\n content: {\n 'application/problem+json': { schema: PROBLEM_DETAILS_SCHEMA }\n }\n };\n }\n\n if (meta.authRoles !== null) {\n if (!result['401']) {\n result['401'] = {\n description: 'Unauthorized',\n content: {\n 'application/problem+json': {\n schema: PROBLEM_DETAILS_SCHEMA\n }\n }\n };\n }\n if (!result['403']) {\n result['403'] = {\n description: 'Forbidden',\n content: {\n 'application/problem+json': {\n schema: PROBLEM_DETAILS_SCHEMA\n }\n }\n };\n }\n }\n\n // Multiple content types — augment each response's content map with extra\n // MIME types from .produces(). producesFile already handled above (binary wins).\n if (meta.produces && !meta.producesFile) {\n for (const code of Object.keys(result)) {\n const entry = result[code] as Record<string, unknown>;\n const existingContent = entry['content'] as\n | Record<string, unknown>\n | undefined;\n // Only augment success responses that already have a content map\n // (skip error responses whose content is application/problem+json)\n if (\n !existingContent ||\n existingContent['application/problem+json']\n ) {\n continue;\n }\n const baseSchema =\n (existingContent['application/json'] as Record<string, unknown>)\n ?.schema ?? {};\n for (const [mimeType, typeEntry] of Object.entries(meta.produces)) {\n if (mimeType === 'application/json') continue;\n existingContent[mimeType] = {\n schema: typeEntry.schema\n ? convertSchema(typeEntry.schema, registry)\n : baseSchema\n };\n }\n }\n }\n\n // Response headers — inject into every response code when declared\n if (meta.responseHeaderSchema) {\n const headers = buildResponseHeaders(\n meta.responseHeaderSchema,\n registry\n );\n for (const code of Object.keys(result)) {\n (result[code] as Record<string, unknown>)['headers'] = headers;\n }\n }\n\n // Links — attach to the lowest 2xx response\n if (meta.links) {\n const linksOut: Record<string, unknown> = {};\n for (const [linkName, linkDef] of Object.entries(meta.links)) {\n const link: Record<string, unknown> = {\n operationId: linkDef.operationId\n };\n if (linkDef.description) link['description'] = linkDef.description;\n if (linkDef.requestBody) link['requestBody'] = linkDef.requestBody;\n if (linkDef.parameters) {\n let params: Record<string, unknown>;\n if (\n typeof linkDef.parameters === 'function' &&\n meta.responseSchema\n ) {\n const tree = ObjectSchemaBuilder.getPropertiesFor(\n meta.responseSchema as ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n );\n const raw = (\n linkDef.parameters as (\n r: any\n ) => Record<string, unknown>\n )(tree);\n params = {};\n for (const [paramName, value] of Object.entries(raw)) {\n if (\n value &&\n typeof value === 'object' &&\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in\n (value as object)\n ) {\n const pointer = (value as any)[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ].toJsonPointer();\n params[paramName] = `$response.body#${pointer}`;\n } else {\n params[paramName] = value;\n }\n }\n } else {\n params = linkDef.parameters as Record<string, unknown>;\n }\n link['parameters'] = params;\n }\n linksOut[linkName] = link;\n }\n const successCode = Object.keys(result)\n .filter(k => Number(k) >= 200 && Number(k) < 300)\n .sort()[0];\n if (successCode) {\n (result[successCode] as Record<string, unknown>)['links'] =\n linksOut;\n }\n }\n\n return result;\n}\n\nfunction buildOperation(\n meta: EndpointMetadata,\n pathParams: { name: string; schema: Record<string, unknown> }[],\n securitySchemeNames: string[],\n registry: SchemaRegistry\n): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // Metadata\n if (meta.summary) operation['summary'] = meta.summary;\n if (meta.description) operation['description'] = meta.description;\n if (meta.tags.length > 0) operation['tags'] = [...meta.tags];\n if (meta.operationId) operation['operationId'] = meta.operationId;\n if (meta.deprecated) operation['deprecated'] = true;\n\n // Parameters\n const parameters: Record<string, unknown>[] = [];\n\n // Path parameters\n for (const pp of pathParams) {\n parameters.push(buildParameterObject(pp.name, 'path', pp.schema, true));\n }\n\n // Query parameters\n if (meta.querySchema) {\n const queryInfo = meta.querySchema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any>\n > = queryInfo.properties ?? {};\n for (const [name, propSchema] of Object.entries(props)) {\n const propInfo = propSchema.introspect() as any;\n const isRequired = propInfo.isRequired !== false;\n const description =\n typeof propInfo.description === 'string' &&\n propInfo.description !== ''\n ? propInfo.description\n : undefined;\n parameters.push(\n buildParameterObject(\n name,\n 'query',\n convertSchema(propSchema, registry),\n isRequired,\n description\n )\n );\n }\n }\n\n // Header parameters\n if (meta.headerSchema) {\n const headerInfo = meta.headerSchema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any>\n > = headerInfo.properties ?? {};\n for (const [name, propSchema] of Object.entries(props)) {\n const propInfo = propSchema.introspect() as any;\n const isRequired = propInfo.isRequired !== false;\n const description =\n typeof propInfo.description === 'string' &&\n propInfo.description !== ''\n ? propInfo.description\n : undefined;\n parameters.push(\n buildParameterObject(\n name,\n 'header',\n convertSchema(propSchema, registry),\n isRequired,\n description\n )\n );\n }\n }\n\n if (parameters.length > 0) operation['parameters'] = parameters;\n\n // Request body\n if (meta.bodySchema) {\n operation['requestBody'] = buildRequestBody(\n meta.bodySchema,\n registry,\n meta.example,\n meta.examples\n );\n }\n\n // Responses\n operation['responses'] = buildResponses(\n meta,\n meta.method.toUpperCase(),\n registry\n );\n\n // Security\n const security = mapOperationSecurity(authRoles(meta), securitySchemeNames);\n if (security.length > 0) operation['security'] = security;\n\n // External documentation\n if (meta.externalDocs) {\n const ed: Record<string, unknown> = { url: meta.externalDocs.url };\n if (meta.externalDocs.description)\n ed['description'] = meta.externalDocs.description;\n operation['externalDocs'] = ed;\n }\n\n // Callbacks\n if (meta.callbacks) {\n const callbacksOut: Record<string, unknown> = {};\n for (const [cbName, cbDef] of Object.entries(meta.callbacks)) {\n // Determine URL expression\n let expression: string;\n if (cbDef.expression) {\n expression = cbDef.expression;\n } else if (cbDef.urlFrom && meta.bodySchema) {\n const tree = ObjectSchemaBuilder.getPropertiesFor(\n meta.bodySchema as ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n );\n const selected = cbDef.urlFrom(tree);\n if (\n selected &&\n typeof selected === 'object' &&\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in (selected as object)\n ) {\n const pointer = (selected as any)[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ].toJsonPointer();\n expression = `{$request.body#${pointer}}`;\n } else {\n expression = String(\n selected ?? '{$request.body#/callbackUrl}'\n );\n }\n } else {\n expression = '{$request.body#/callbackUrl}';\n }\n\n const cbMethod = (cbDef.method ?? 'post').toLowerCase();\n const cbOperation: Record<string, unknown> = {};\n if (cbDef.summary) cbOperation['summary'] = cbDef.summary;\n if (cbDef.description)\n cbOperation['description'] = cbDef.description;\n if (cbDef.body) {\n cbOperation['requestBody'] = buildRequestBody(\n cbDef.body,\n registry\n );\n }\n const cbResponses: Record<string, unknown> = {};\n if (cbDef.response) {\n cbResponses['200'] = {\n description: 'OK',\n content: {\n 'application/json': {\n schema: convertSchema(cbDef.response, registry)\n }\n }\n };\n } else {\n cbResponses['200'] = { description: 'OK' };\n }\n cbOperation['responses'] = cbResponses;\n callbacksOut[cbName] = {\n [expression]: { [cbMethod]: cbOperation }\n };\n }\n operation['callbacks'] = callbacksOut;\n }\n\n return operation;\n}\n\nfunction authRoles(meta: EndpointMetadata): readonly string[] | null {\n return meta.authRoles;\n}\n\n/**\n * Generate an OpenAPI 3.1 specification document from registered endpoints.\n */\nexport function generateOpenApiSpec(options: OpenApiOptions): OpenApiDocument {\n const srv = options.server;\n const registrations =\n options.registrations ?? srv?.getRegistrations() ?? [];\n const authConfig =\n options.authConfig !== undefined\n ? options.authConfig\n : (srv?.getAuthenticationConfig() ?? undefined);\n const webhooks =\n options.webhooks ??\n (srv && srv.getWebhooks().length > 0\n ? srv.getWebhooks()\n : undefined);\n const { info, servers, securitySchemes, tags } = options;\n\n // Security schemes — from explicit config or auto-mapped\n const resolvedSchemes: Record<string, OpenApiSecurityScheme> =\n securitySchemes ?? mapSecuritySchemes(authConfig);\n const securitySchemeNames = Object.keys(resolvedSchemes);\n\n // Pre-pass: collect all named schemas from every endpoint into a registry.\n // Walking happens before path generation so that $ref pointers are emitted\n // correctly at every call site within buildOperation.\n const registry = new SchemaRegistry();\n const visited = new Set<SchemaBuilder<any, any, any>>();\n for (const reg of registrations) {\n const meta = reg.endpoint;\n if (meta.bodySchema) walkSchemas(meta.bodySchema, registry, visited);\n if (meta.responseSchema)\n walkSchemas(meta.responseSchema, registry, visited);\n if (meta.responsesSchemas) {\n for (const schema of Object.values(meta.responsesSchemas)) {\n if (schema) walkSchemas(schema, registry, visited);\n }\n }\n if (meta.querySchema) {\n const queryProps =\n (meta.querySchema.introspect() as any).properties ?? {};\n for (const propSchema of Object.values<\n SchemaBuilder<any, any, any>\n >(queryProps)) {\n walkSchemas(propSchema, registry, visited);\n }\n }\n if (meta.headerSchema) {\n const headerProps =\n (meta.headerSchema.introspect() as any).properties ?? {};\n for (const propSchema of Object.values<\n SchemaBuilder<any, any, any>\n >(headerProps)) {\n walkSchemas(propSchema, registry, visited);\n }\n }\n if (meta.callbacks) {\n for (const cbDef of Object.values(meta.callbacks)) {\n if (cbDef.body) walkSchemas(cbDef.body, registry, visited);\n if (cbDef.response)\n walkSchemas(cbDef.response, registry, visited);\n }\n }\n }\n if (webhooks) {\n for (const webhook of webhooks) {\n if (webhook.body) walkSchemas(webhook.body, registry, visited);\n if (webhook.response)\n walkSchemas(webhook.response, registry, visited);\n }\n }\n\n const resolveComponentSchemaName = (\n rootSchema: SchemaBuilder<any, any, any>\n ) => {\n // The root schema must be inlined exactly once — for the component\n // definition itself. Any subsequent encounter (e.g. through a lazy\n // self-reference) should emit a $ref instead of inlining again,\n // which would otherwise cause infinite recursion.\n let inlinedRoot = false;\n return (\n candidate: SchemaBuilder<any, any, any>\n ): string | undefined => {\n if (candidate === rootSchema && !inlinedRoot) {\n inlinedRoot = true;\n return undefined; // inline the root definition once\n }\n return registry.getName(candidate) ?? undefined;\n };\n };\n\n // Build paths\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const reg of registrations) {\n const meta = reg.endpoint;\n const { path, parameters: pathParams } = resolvePath(meta);\n const method = meta.method.toLowerCase();\n\n if (!paths[path]) paths[path] = {};\n paths[path][method] = buildOperation(\n meta,\n pathParams as { name: string; schema: Record<string, unknown> }[],\n securitySchemeNames,\n registry\n );\n }\n\n // Build top-level tags array: explicit entries first, then auto-collected\n // tag names from endpoints that are not already covered.\n const explicitNames = new Set((tags ?? []).map(t => t.name));\n const autoNames: string[] = [];\n for (const reg of registrations) {\n for (const tag of reg.endpoint.tags) {\n if (!explicitNames.has(tag) && !autoNames.includes(tag)) {\n autoNames.push(tag);\n }\n }\n }\n autoNames.sort();\n const mergedTags: OpenApiTag[] = [\n ...(tags ?? []),\n ...autoNames.map(name => ({ name }))\n ];\n\n // Assemble document\n const doc: OpenApiDocument = {\n openapi: '3.1.0',\n info: { ...info }\n };\n\n if (servers && servers.length > 0) {\n doc['servers'] = servers.map(s => ({ ...s }));\n }\n\n if (mergedTags.length > 0) {\n doc['tags'] = mergedTags.map(t => ({ ...t }));\n }\n\n doc['paths'] = paths;\n\n // Webhooks — out-of-band async requests documented alongside paths\n if (webhooks && webhooks.length > 0) {\n const webhooksObj: Record<string, unknown> = {};\n for (const webhook of webhooks) {\n const whMethod = (webhook.method ?? 'post').toLowerCase();\n const whOperation: Record<string, unknown> = {};\n if (webhook.summary) whOperation['summary'] = webhook.summary;\n if (webhook.description)\n whOperation['description'] = webhook.description;\n if (webhook.tags && webhook.tags.length > 0)\n whOperation['tags'] = [...webhook.tags];\n if (webhook.body) {\n whOperation['requestBody'] = buildRequestBody(\n webhook.body,\n registry\n );\n }\n const whResponses: Record<string, unknown> = {};\n if (webhook.response) {\n whResponses['200'] = {\n description: 'OK',\n content: {\n 'application/json': {\n schema: convertSchema(webhook.response, registry)\n }\n }\n };\n } else {\n whResponses['200'] = { description: 'OK' };\n }\n whOperation['responses'] = whResponses;\n webhooksObj[webhook.name] = { [whMethod]: whOperation };\n }\n doc['webhooks'] = webhooksObj;\n }\n\n // Components — security schemes + named component schemas\n const componentSchemas: Record<string, unknown> = {};\n for (const [name, schema] of registry.entries()) {\n // Inline the root schema to avoid a self-referential $ref, but resolve\n // nested named schemas through the shared registry so component\n // definitions can still deduplicate via $ref.\n componentSchemas[name] = convertSchema(\n schema,\n resolveComponentSchemaName(schema)\n );\n }\n const hasSchemas = Object.keys(componentSchemas).length > 0;\n\n if (securitySchemeNames.length > 0 || hasSchemas) {\n const components: Record<string, unknown> = {};\n if (securitySchemeNames.length > 0)\n components['securitySchemes'] = { ...resolvedSchemes };\n if (hasSchemas) components['schemas'] = componentSchemas;\n doc['components'] = components;\n }\n\n return doc;\n}\n","import type {\n ParseStringSchemaBuilder,\n SchemaBuilder\n} from '@cleverbrush/schema';\nimport { toJsonSchema } from '@cleverbrush/schema-json';\nimport type { EndpointMetadata } from '@cleverbrush/server';\n\ntype RoutePath = string | ParseStringSchemaBuilder<any, any, any, any, any>;\n\nfunction isParseStringSchema(\n p: RoutePath\n): p is ParseStringSchemaBuilder<any, any, any, any, any> {\n return typeof p !== 'string' && typeof (p as any).validate === 'function';\n}\n\n/**\n * Describes a single path parameter extracted from a route template,\n * including its name and the JSON Schema representation of its type.\n */\nexport interface PathParameterInfo {\n readonly name: string;\n readonly schema: Record<string, unknown>;\n}\n\n/**\n * The output of resolving a route path template to an OpenAPI-compatible\n * path string and its parameter list.\n */\nexport interface ResolvedPath {\n /** OpenAPI-formatted path, e.g. `/api/users/{id}` */\n readonly path: string;\n /** Extracted path parameters with their JSON Schema */\n readonly parameters: readonly PathParameterInfo[];\n}\n\n/**\n * Convert a colon-style static path to OpenAPI `{param}` format.\n * E.g. `/users/:id/posts/:pid` → `/users/{id}/posts/{pid}`\n */\nfunction convertColonParams(path: string): {\n converted: string;\n paramNames: string[];\n} {\n const paramNames: string[] = [];\n const converted = path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {\n paramNames.push(name);\n return `{${name}}`;\n });\n return { converted, paramNames };\n}\n\n/**\n * Convert a `ParseStringSchemaBuilder` path template to an OpenAPI-formatted\n * path string and extract parameter schemas.\n */\nfunction convertParseStringPath(\n pathTemplate: ParseStringSchemaBuilder<any, any, any, any, any>\n): { pathString: string; parameters: PathParameterInfo[] } {\n const info = pathTemplate.introspect() as any;\n const templateDef = info.templateDefinition as {\n literals: string[];\n segments: { path: string }[];\n };\n const objectSchema = info.objectSchema;\n const objInfo = objectSchema.introspect() as any;\n const props: Record<\n string,\n SchemaBuilder<any, any, any>\n > = objInfo.properties ?? {};\n\n let pathString = '';\n for (let i = 0; i < templateDef.segments.length; i++) {\n pathString +=\n templateDef.literals[i] + `{${templateDef.segments[i].path}}`;\n }\n pathString += templateDef.literals[templateDef.segments.length] ?? '';\n\n const parameters: PathParameterInfo[] = templateDef.segments.map(seg => ({\n name: seg.path,\n schema: props[seg.path]\n ? toJsonSchema(props[seg.path], { $schema: false })\n : { type: 'string' }\n }));\n\n return { pathString, parameters };\n}\n\n/**\n * Combines `basePath` and `pathTemplate` from an endpoint into an\n * OpenAPI-formatted path with extracted parameter information.\n */\nexport function resolvePath(meta: EndpointMetadata): ResolvedPath {\n const basePath = meta.basePath.replace(/\\/$/, '');\n const pathTemplate = meta.pathTemplate;\n\n if (isParseStringSchema(pathTemplate)) {\n const { pathString, parameters } = convertParseStringPath(pathTemplate);\n const fullPath = normalizeSlashes(basePath + pathString);\n return { path: fullPath, parameters };\n }\n\n // Static string path template\n const templateStr = pathTemplate === '/' ? '' : pathTemplate;\n const combined = basePath + templateStr;\n const { converted, paramNames } = convertColonParams(combined);\n const fullPath = normalizeSlashes(converted);\n\n const parameters: PathParameterInfo[] = paramNames.map(name => ({\n name,\n schema: { type: 'string' }\n }));\n\n return { path: fullPath, parameters };\n}\n\nfunction normalizeSlashes(path: string): string {\n // Replace double slashes with single, ensure leading slash\n let result = path.replace(/\\/+/g, '/');\n if (!result.startsWith('/')) result = '/' + result;\n if (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1);\n return result || '/';\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport { toJsonSchema } from '@cleverbrush/schema-json';\nimport type { SchemaRegistry } from './schemaRegistry.js';\n\ntype NameResolver = (\n schema: SchemaBuilder<any, any, any>\n) => string | null | undefined;\n\n/**\n * Converts a `@cleverbrush/schema` builder to a JSON Schema object suitable\n * for embedding in an OpenAPI 3.1 spec (no `$schema` header, Draft 2020-12).\n *\n * Returns an empty schema `{}` when the input is `null` or `undefined`.\n *\n * When a {@link SchemaRegistry} or a custom resolver function is provided, any\n * schema instance that resolves to a name will be emitted as a\n * `$ref: '#/components/schemas/<name>'` pointer instead of being inlined.\n *\n * @param schema - The schema to convert, or `null`/`undefined`.\n * @param registry - Optional registry or resolver function for `$ref` deduplication.\n */\nexport function convertSchema(\n schema: SchemaBuilder<any, any, any, any, any> | null | undefined,\n registry?: SchemaRegistry | NameResolver\n): Record<string, unknown> {\n if (schema == null) return {};\n\n let nameResolver:\n | ((s: SchemaBuilder<any, any, any>) => string | null)\n | undefined;\n if (registry) {\n if (typeof registry === 'function') {\n nameResolver = s => registry(s) ?? null;\n } else {\n nameResolver = s => registry.getName(s);\n }\n }\n\n return toJsonSchema(schema, {\n $schema: false,\n draft: '2020-12',\n nameResolver\n });\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\n\n// ---------------------------------------------------------------------------\n// SchemaRegistry\n// ---------------------------------------------------------------------------\n\n/**\n * Collects schemas that carry an explicit component name (set via\n * `.schemaName()`) and provides a reference-based lookup used during OpenAPI\n * spec generation to replace inline schema objects with\n * `$ref: '#/components/schemas/<name>'` pointers.\n *\n * **Conflict rule**: registering two *different* schema instances (different\n * object references) under the same name throws immediately. Re-registering\n * the same instance is a no-op.\n *\n * @example\n * ```ts\n * const registry = new SchemaRegistry();\n * registry.register(UserSchema); // UserSchema.schemaName('User')\n * registry.getName(UserSchema); // 'User'\n * registry.getName(someOtherSchema); // null\n * ```\n */\nexport class SchemaRegistry {\n /** schema instance → registered name */\n private readonly byInstance = new Map<\n SchemaBuilder<any, any, any>,\n string\n >();\n /** name → first-registered schema instance */\n private readonly byName = new Map<string, SchemaBuilder<any, any, any>>();\n\n /**\n * Attempts to register `schema` in the registry.\n *\n * - If the schema has no `schemaName` in its introspect output, it is\n * silently skipped.\n * - If the same instance is already registered, this is a no-op.\n * - If a **different** instance is already registered under the same name,\n * an error is thrown.\n *\n * @param schema - The schema builder to register.\n * @throws {Error} When two distinct schema instances share the same name.\n */\n register(schema: SchemaBuilder<any, any, any>): void {\n const name = (schema.introspect() as any).schemaName as\n | string\n | undefined;\n if (typeof name !== 'string') return;\n\n const existing = this.byName.get(name);\n if (existing !== undefined) {\n // Same instance → idempotent, nothing to do\n if (existing === schema) return;\n // Different instance → conflict\n throw new Error(\n `Schema name \"${name}\" is already registered by a different schema instance. ` +\n `Each named schema must be a single, reused constant. ` +\n `If you intended to register the same schema, ensure you are passing ` +\n `the same object reference (not a rebuilt schema).`\n );\n }\n\n this.byInstance.set(schema, name);\n this.byName.set(name, schema);\n }\n\n /**\n * Returns the component name for a given schema instance, or `null` if it\n * was not registered.\n *\n * @param schema - The schema builder to look up.\n * @returns The registered name, or `null`.\n */\n getName(schema: SchemaBuilder<any, any, any>): string | null {\n return this.byInstance.get(schema) ?? null;\n }\n\n /**\n * Iterates over all registered `[name, schema]` pairs in insertion order.\n *\n * Used to emit the `components.schemas` section of an OpenAPI document.\n */\n entries(): IterableIterator<[string, SchemaBuilder<any, any, any>]> {\n return this.byName.entries();\n }\n\n /** Returns `true` when at least one schema has been registered. */\n get isEmpty(): boolean {\n return this.byName.size === 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// walkSchemas\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively visits every {@link SchemaBuilder} reachable from `schema` and\n * calls {@link SchemaRegistry.register} on each node.\n *\n * Cycle detection is performed via a `visited` `Set` of object references, so\n * schemas may safely be shared across multiple branches without causing\n * infinite recursion.\n *\n * **Excluded schema types**\n * - `lazy` — deferred resolution would require calling the getter, which may\n * itself reference the parent schema; lazy schemas are handled separately.\n *\n * @param schema - Root schema to start the walk from.\n * @param registry - Registry to register named schemas into.\n * @param visited - Shared set for cycle detection; pass a new `Set()` for the\n * top-level call.\n */\nexport function walkSchemas(\n schema: SchemaBuilder<any, any, any>,\n registry: SchemaRegistry,\n visited: Set<SchemaBuilder<any, any, any>> = new Set()\n): void {\n if (visited.has(schema)) return;\n visited.add(schema);\n\n registry.register(schema);\n\n const info = schema.introspect() as any;\n\n switch (info.type) {\n case 'object': {\n const props = info.properties as\n | Record<string, SchemaBuilder<any, any, any>>\n | undefined;\n if (props) {\n for (const child of Object.values(props)) {\n walkSchemas(child, registry, visited);\n }\n }\n break;\n }\n case 'array':\n if (info.elementSchema) {\n walkSchemas(info.elementSchema, registry, visited);\n }\n break;\n case 'tuple': {\n const elements: SchemaBuilder<any, any, any>[] =\n info.elements ?? [];\n for (const el of elements) {\n walkSchemas(el, registry, visited);\n }\n if (info.restSchema) {\n walkSchemas(info.restSchema, registry, visited);\n }\n break;\n }\n case 'union': {\n const options: SchemaBuilder<any, any, any>[] = info.options ?? [];\n for (const opt of options) {\n walkSchemas(opt, registry, visited);\n }\n break;\n }\n case 'record':\n if (info.keySchema) {\n walkSchemas(info.keySchema, registry, visited);\n }\n if (info.valueSchema) {\n walkSchemas(info.valueSchema, registry, visited);\n }\n break;\n case 'lazy': {\n // Resolve the inner schema and walk it so that any named schema\n // reachable through a lazy boundary is registered. Cycle-safety is\n // provided by the `visited` set — the lazy wrapper itself was\n // already added above, preventing infinite recursion on\n // self-referential schemas.\n const resolved = (schema as any).resolve() as SchemaBuilder<\n any,\n any,\n any\n >;\n walkSchemas(resolved, registry, visited);\n break;\n }\n default:\n break;\n }\n}\n","import type { AuthenticationConfig } from '@cleverbrush/server';\n\n/**\n * An OpenAPI 3.1 Security Scheme Object.\n * Describes how requests to the API are authenticated.\n *\n * @see {@link https://spec.openapis.org/oas/v3.1.0#security-scheme-object OpenAPI Security Scheme Object}\n */\nexport interface OpenApiSecurityScheme {\n readonly type: string;\n readonly scheme?: string;\n readonly bearerFormat?: string;\n readonly in?: string;\n readonly name?: string;\n readonly flows?: Record<string, unknown>;\n readonly openIdConnectUrl?: string;\n}\n\n/**\n * Map `@cleverbrush/auth` authentication schemes to OpenAPI security scheme objects.\n */\nexport function mapSecuritySchemes(\n authConfig: AuthenticationConfig | null | undefined\n): Record<string, OpenApiSecurityScheme> {\n if (!authConfig) return {};\n\n const result: Record<string, OpenApiSecurityScheme> = {};\n for (const scheme of authConfig.schemes) {\n const name = scheme.name;\n const challenge = scheme.challenge?.();\n\n if ('openIdConnectUrl' in scheme && (scheme as any).openIdConnectUrl) {\n result[name] = {\n type: 'openIdConnect',\n openIdConnectUrl: (scheme as any).openIdConnectUrl\n };\n } else if ('flows' in scheme && (scheme as any).flows) {\n result[name] = {\n type: 'oauth2',\n flows: (scheme as any).flows\n };\n } else if (\n challenge?.headerValue?.toLowerCase().startsWith('bearer') ||\n name === 'jwt'\n ) {\n result[name] = {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT'\n };\n } else if (name === 'cookie' || (scheme as any).cookieName) {\n // Cookie scheme — extract cookie name from options if available\n const cookieName =\n (scheme as any).cookieName ??\n (scheme as any)._options?.cookieName ??\n 'session';\n result[name] = {\n type: 'apiKey',\n in: 'cookie',\n name: cookieName\n };\n } else if (challenge) {\n // Generic scheme with a challenge header\n result[name] = {\n type: 'http',\n scheme: challenge.headerValue.split(' ')[0].toLowerCase()\n };\n } else {\n // Fallback — treat as generic HTTP scheme\n result[name] = { type: 'http', scheme: name };\n }\n }\n return result;\n}\n\n/**\n * Map endpoint `authRoles` to an OpenAPI operation-level `security` array.\n *\n * - `null` → empty array (public endpoint, no security)\n * - `[]` → `[{ <schemeName>: [] }]` (any authenticated user)\n * - `['admin']` → `[{ <schemeName>: ['admin'] }]` (require specific roles)\n */\nexport function mapOperationSecurity(\n authRoles: readonly string[] | null,\n securitySchemeNames: string[]\n): Record<string, string[]>[] {\n if (authRoles === null) return [];\n if (securitySchemeNames.length === 0) return [];\n\n // Each security scheme listed as an option (OR semantics in OpenAPI)\n return securitySchemeNames.map(name => ({\n [name]: [...authRoles]\n }));\n}\n","import type {\n AuthenticationConfig,\n EndpointRegistration\n} from '@cleverbrush/server';\nimport { endpoint } from '@cleverbrush/server';\nimport {\n generateOpenApiSpec,\n type OpenApiDocument,\n type OpenApiInfo,\n type OpenApiServer,\n type OpenApiServer_ServerLike\n} from './generateOpenApiSpec.js';\nimport type { OpenApiSecurityScheme } from './securityMapper.js';\n\n/**\n * Options for {@link createOpenApiEndpoint}.\n *\n * When `server` is provided, `getRegistrations` and `authConfig` are derived\n * from it automatically (unless explicitly overridden).\n */\nexport interface OpenApiEndpointOptions {\n /**\n * A `ServerBuilder` (or any structurally compatible object). When set,\n * endpoint registrations and auth config are read from the server\n * automatically. Explicit `getRegistrations` / `authConfig` values\n * take precedence.\n */\n readonly server?: OpenApiServer_ServerLike;\n /** Function that returns endpoint registrations. */\n readonly getRegistrations?: () => readonly EndpointRegistration[];\n /** OpenAPI info metadata. */\n readonly info: OpenApiInfo;\n /** Optional server entries. */\n readonly servers?: readonly OpenApiServer[];\n /** Optional auth config for security scheme generation. */\n readonly authConfig?: AuthenticationConfig | null;\n /** Override security schemes manually. */\n readonly securitySchemes?: Record<string, OpenApiSecurityScheme>;\n /** Path to serve the spec at (default: `/openapi.json`). */\n readonly path?: string;\n}\n\n/**\n * Creates an endpoint definition and handler that serves the OpenAPI spec\n * as JSON. Register it with `builder.handle(ep, handler)`.\n *\n * The spec is lazily generated on first request and cached.\n *\n * @example\n * ```ts\n * const { endpoint: openApiEp, handler } = createOpenApiEndpoint({\n * server,\n * info: { title: 'My API', version: '1.0.0' }\n * });\n * server.handle(openApiEp, handler);\n * ```\n */\nexport function createOpenApiEndpoint(options: OpenApiEndpointOptions): {\n endpoint: ReturnType<(typeof endpoint)['get']>;\n handler: () => OpenApiDocument;\n} {\n const servePath = options.path ?? '/openapi.json';\n const srv = options.server;\n const getRegistrations =\n options.getRegistrations ??\n (srv ? () => srv.getRegistrations() : () => []);\n\n const ep = endpoint\n .get(servePath)\n .summary('OpenAPI specification')\n .tags('OpenAPI')\n .operationId('getOpenApiSpec');\n\n let cachedSpec: OpenApiDocument | null = null;\n\n const handler = (): OpenApiDocument => {\n if (!cachedSpec) {\n cachedSpec = generateOpenApiSpec({\n registrations: getRegistrations(),\n info: options.info,\n servers: options.servers,\n authConfig:\n options.authConfig !== undefined\n ? options.authConfig\n : (srv?.getAuthenticationConfig() ?? undefined),\n securitySchemes: options.securitySchemes\n });\n }\n return cachedSpec;\n };\n\n return { endpoint: ep, handler };\n}\n","import type {\n AuthenticationConfig,\n EndpointRegistration,\n RequestContext\n} from '@cleverbrush/server';\nimport {\n generateOpenApiSpec,\n type OpenApiDocument,\n type OpenApiInfo,\n type OpenApiServer,\n type OpenApiServer_ServerLike\n} from './generateOpenApiSpec.js';\nimport type { OpenApiSecurityScheme } from './securityMapper.js';\n\n/**\n * Options for the {@link serveOpenApi} middleware.\n *\n * When `server` is provided, `getRegistrations` and `authConfig` are derived\n * from it automatically (unless explicitly overridden).\n */\nexport interface ServeOpenApiOptions {\n /**\n * A `ServerBuilder` (or any structurally compatible object). When set,\n * endpoint registrations and auth config are read from the server\n * automatically. Explicit `getRegistrations` / `authConfig` values\n * take precedence.\n */\n readonly server?: OpenApiServer_ServerLike;\n /** Function that returns endpoint registrations. */\n readonly getRegistrations?: () => readonly EndpointRegistration[];\n /** OpenAPI info metadata. */\n readonly info: OpenApiInfo;\n /** Optional server entries. */\n readonly servers?: readonly OpenApiServer[];\n /** Optional auth config for security scheme generation. */\n readonly authConfig?: AuthenticationConfig | null;\n /** Override security schemes manually. */\n readonly securitySchemes?: Record<string, OpenApiSecurityScheme>;\n /** Path to serve the spec at (default: `/openapi.json`). */\n readonly path?: string;\n}\n\n/**\n * Returns a server middleware that serves the OpenAPI spec as JSON at\n * the configured path (default: `/openapi.json`).\n *\n * The spec is lazily generated on first request and cached.\n */\nexport function serveOpenApi(\n options: ServeOpenApiOptions\n): (context: RequestContext, next: () => Promise<void>) => Promise<void> {\n const servePath = options.path ?? '/openapi.json';\n const srv = options.server;\n const getRegistrations =\n options.getRegistrations ??\n (srv ? () => srv.getRegistrations() : () => []);\n let cachedSpec: OpenApiDocument | null = null;\n\n return async (context, next) => {\n const url = context.url;\n const pathname = url.pathname;\n const method = context.method.toUpperCase();\n\n if (method === 'GET' && pathname === servePath) {\n if (!cachedSpec) {\n cachedSpec = generateOpenApiSpec({\n registrations: getRegistrations(),\n info: options.info,\n servers: options.servers,\n authConfig:\n options.authConfig !== undefined\n ? options.authConfig\n : (srv?.getAuthenticationConfig() ?? undefined),\n securitySchemes: options.securitySchemes\n });\n }\n const body = JSON.stringify(cachedSpec);\n context.response.writeHead(200, {\n 'content-type': 'application/json',\n 'content-length': Buffer.byteLength(body).toString()\n });\n context.response.end(body);\n return;\n }\n\n await next();\n };\n}\n"],"mappings":"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","names":["fs","path","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","toJsonSchema","isParseStringSchema","p","convertColonParams","path","paramNames","_","name","convertParseStringPath","pathTemplate","info","templateDef","props","pathString","i","parameters","seg","resolvePath","meta","basePath","normalizeSlashes","combined","converted","fullPath","result","toJsonSchema","convertSchema","schema","registry","nameResolver","s","SchemaRegistry","schema","name","existing","walkSchemas","registry","visited","info","props","child","elements","el","options","opt","resolved","mapSecuritySchemes","authConfig","result","scheme","name","challenge","cookieName","mapOperationSecurity","authRoles","securitySchemeNames","buildParameterObject","name","location","schema","required","description","param","buildRequestBody","bodySchema","registry","example","examples","jsonSchema","convertSchema","bodyInfo","mediaType","body","HTTP_STATUS_DESCRIPTIONS","PROBLEM_DETAILS_SCHEMA","buildResponseHeaders","props","headers","propSchema","propInfo","header","buildResponses","meta","method","result","ct","desc","codeStr","code","respInfo","customDesc","existingContent","baseSchema","mimeType","typeEntry","linksOut","linkName","linkDef","link","params","tree","ObjectSchemaBuilder","raw","paramName","value","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","pointer","successCode","k","buildOperation","pathParams","securitySchemeNames","operation","parameters","pp","isRequired","security","mapOperationSecurity","authRoles","ed","callbacksOut","cbName","cbDef","expression","selected","cbMethod","cbOperation","cbResponses","generateOpenApiSpec","options","srv","registrations","authConfig","webhooks","info","servers","securitySchemes","tags","resolvedSchemes","mapSecuritySchemes","SchemaRegistry","visited","reg","walkSchemas","queryProps","headerProps","webhook","resolveComponentSchemaName","rootSchema","inlinedRoot","candidate","paths","path","resolvePath","explicitNames","t","autoNames","tag","mergedTags","doc","s","webhooksObj","whMethod","whOperation","whResponses","componentSchemas","hasSchemas","components","writeOpenApiSpec","options","outputPath","spec","generateOpenApiSpec","dir","endpoint","createOpenApiEndpoint","options","servePath","srv","getRegistrations","ep","endpoint","cachedSpec","generateOpenApiSpec","serveOpenApi","options","servePath","srv","getRegistrations","cachedSpec","context","next","pathname","generateOpenApiSpec","body"]}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import type { AuthenticationConfig, EndpointRegistration } from '@cleverbrush/server';
|
|
2
2
|
import { endpoint } from '@cleverbrush/server';
|
|
3
|
-
import { type OpenApiDocument, type OpenApiInfo, type OpenApiServer } from './generateOpenApiSpec.js';
|
|
3
|
+
import { type OpenApiDocument, type OpenApiInfo, type OpenApiServer, type OpenApiServer_ServerLike } from './generateOpenApiSpec.js';
|
|
4
4
|
import type { OpenApiSecurityScheme } from './securityMapper.js';
|
|
5
5
|
/**
|
|
6
6
|
* Options for {@link createOpenApiEndpoint}.
|
|
7
|
+
*
|
|
8
|
+
* When `server` is provided, `getRegistrations` and `authConfig` are derived
|
|
9
|
+
* from it automatically (unless explicitly overridden).
|
|
7
10
|
*/
|
|
8
11
|
export interface OpenApiEndpointOptions {
|
|
12
|
+
/**
|
|
13
|
+
* A `ServerBuilder` (or any structurally compatible object). When set,
|
|
14
|
+
* endpoint registrations and auth config are read from the server
|
|
15
|
+
* automatically. Explicit `getRegistrations` / `authConfig` values
|
|
16
|
+
* take precedence.
|
|
17
|
+
*/
|
|
18
|
+
readonly server?: OpenApiServer_ServerLike;
|
|
9
19
|
/** Function that returns endpoint registrations. */
|
|
10
|
-
readonly getRegistrations
|
|
20
|
+
readonly getRegistrations?: () => readonly EndpointRegistration[];
|
|
11
21
|
/** OpenAPI info metadata. */
|
|
12
22
|
readonly info: OpenApiInfo;
|
|
13
23
|
/** Optional server entries. */
|
|
@@ -28,10 +38,10 @@ export interface OpenApiEndpointOptions {
|
|
|
28
38
|
* @example
|
|
29
39
|
* ```ts
|
|
30
40
|
* const { endpoint: openApiEp, handler } = createOpenApiEndpoint({
|
|
31
|
-
*
|
|
41
|
+
* server,
|
|
32
42
|
* info: { title: 'My API', version: '1.0.0' }
|
|
33
43
|
* });
|
|
34
|
-
*
|
|
44
|
+
* server.handle(openApiEp, handler);
|
|
35
45
|
* ```
|
|
36
46
|
*/
|
|
37
47
|
export declare function createOpenApiEndpoint(options: OpenApiEndpointOptions): {
|
package/dist/serveOpenApi.d.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import type { AuthenticationConfig, EndpointRegistration, RequestContext } from '@cleverbrush/server';
|
|
2
|
-
import { type OpenApiInfo, type OpenApiServer } from './generateOpenApiSpec.js';
|
|
2
|
+
import { type OpenApiInfo, type OpenApiServer, type OpenApiServer_ServerLike } from './generateOpenApiSpec.js';
|
|
3
3
|
import type { OpenApiSecurityScheme } from './securityMapper.js';
|
|
4
4
|
/**
|
|
5
5
|
* Options for the {@link serveOpenApi} middleware.
|
|
6
|
+
*
|
|
7
|
+
* When `server` is provided, `getRegistrations` and `authConfig` are derived
|
|
8
|
+
* from it automatically (unless explicitly overridden).
|
|
6
9
|
*/
|
|
7
10
|
export interface ServeOpenApiOptions {
|
|
11
|
+
/**
|
|
12
|
+
* A `ServerBuilder` (or any structurally compatible object). When set,
|
|
13
|
+
* endpoint registrations and auth config are read from the server
|
|
14
|
+
* automatically. Explicit `getRegistrations` / `authConfig` values
|
|
15
|
+
* take precedence.
|
|
16
|
+
*/
|
|
17
|
+
readonly server?: OpenApiServer_ServerLike;
|
|
8
18
|
/** Function that returns endpoint registrations. */
|
|
9
|
-
readonly getRegistrations
|
|
19
|
+
readonly getRegistrations?: () => readonly EndpointRegistration[];
|
|
10
20
|
/** OpenAPI info metadata. */
|
|
11
21
|
readonly info: OpenApiInfo;
|
|
12
22
|
/** Optional server entries. */
|
package/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"email": "andrew_zol@cleverbrush.com"
|
|
6
6
|
},
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@cleverbrush/server": "0.0.0-beta-
|
|
9
|
-
"@cleverbrush/schema": "0.0.0-beta-
|
|
10
|
-
"@cleverbrush/schema-json": "0.0.0-beta-
|
|
11
|
-
"@cleverbrush/auth": "0.0.0-beta-
|
|
8
|
+
"@cleverbrush/server": "0.0.0-beta-20260416114445",
|
|
9
|
+
"@cleverbrush/schema": "0.0.0-beta-20260416114445",
|
|
10
|
+
"@cleverbrush/schema-json": "0.0.0-beta-20260416114445",
|
|
11
|
+
"@cleverbrush/auth": "0.0.0-beta-20260416114445"
|
|
12
12
|
},
|
|
13
13
|
"description": "OpenAPI 3.1 spec generation for @cleverbrush/server — automatic endpoint documentation",
|
|
14
14
|
"files": [
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
},
|
|
46
46
|
"type": "module",
|
|
47
47
|
"types": "./dist/index.d.ts",
|
|
48
|
-
"version": "0.0.0-beta-
|
|
48
|
+
"version": "0.0.0-beta-20260416114445"
|
|
49
49
|
}
|