@fluojs/openapi 1.0.3 → 2.0.0

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.
@@ -3,32 +3,40 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
3
3
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4
4
  function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
5
5
  function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
6
- import { Controller, Get, NotFoundException, createHandlerMapping } from '@fluojs/http';
7
6
  import { Inject } from '@fluojs/core';
7
+ import { Controller, createHandlerMapping, Get, NotFoundException } from '@fluojs/http';
8
8
  import { defineModule } from '@fluojs/runtime';
9
9
  import { OpenApiHandlerRegistry } from './handler-registry.js';
10
10
  import { buildOpenApiDocument } from './schema-builder.js';
11
11
  import { cloneSnapshotValue, createFrozenSnapshot } from './snapshot.js';
12
- const SWAGGER_UI_DIST_VERSION = '5.32.2';
13
- const SWAGGER_UI_DIST_BASE_URL = `https://unpkg.com/swagger-ui-dist@${SWAGGER_UI_DIST_VERSION}`;
14
- const SWAGGER_UI_CSS_URL = `${SWAGGER_UI_DIST_BASE_URL}/swagger-ui.css`;
15
- const SWAGGER_UI_BUNDLE_JS_URL = `${SWAGGER_UI_DIST_BASE_URL}/swagger-ui-bundle.js`;
12
+ import { createSwaggerUiHtml, resolveSwaggerUiAssets } from './swagger-ui.js';
13
+ const DEFAULT_DOCUMENT_PATH = '/openapi.json';
14
+ const DEFAULT_UI_PATH = '/docs';
16
15
 
17
16
  /**
18
- * Asset URLs used by the generated Swagger UI HTML page.
17
+ * Routes owned by one `OpenApiModule` registration.
18
+ *
19
+ * @remarks
20
+ * Paths use the normal `@fluojs/http` route grammar and are normalized before
21
+ * registration. The defaults remain `/openapi.json` and `/docs`.
19
22
  */
20
23
 
21
24
  /**
22
- * Public options for `OpenApiModule.forRoot(...)` and `OpenApiModule.forRootAsync(...)`.
25
+ * Public document and route options for `OpenApiModule.forRoot(...)`.
23
26
  *
24
27
  * @remarks
25
28
  * Keep README examples for full controller/module workflows. These options are
26
29
  * intended to document the runtime hooks that shape the generated document.
27
30
  */
28
31
 
29
- function escapeHtml(value) {
30
- return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;');
31
- }
32
+ /**
33
+ * Async OpenAPI registration options with routes fixed before module compilation.
34
+ *
35
+ * @remarks
36
+ * `documentPath` and `uiPath` belong to the outer registration because HTTP
37
+ * routes are compiled before the injected options factory resolves.
38
+ */
39
+
32
40
  function cloneRecord(record) {
33
41
  if (!record) {
34
42
  return undefined;
@@ -43,6 +51,7 @@ function snapshotOpenApiModuleOptions(options) {
43
51
  return createFrozenSnapshot({
44
52
  defaultErrorResponsesPolicy: options.defaultErrorResponsesPolicy,
45
53
  descriptors: options.descriptors ? cloneSnapshotValue(options.descriptors) : undefined,
54
+ documentPath: options.documentPath,
46
55
  documentTransform: options.documentTransform,
47
56
  extraModels: options.extraModels ? [...options.extraModels] : undefined,
48
57
  securitySchemes: cloneRecord(options.securitySchemes),
@@ -52,38 +61,17 @@ function snapshotOpenApiModuleOptions(options) {
52
61
  } : undefined,
53
62
  title: options.title,
54
63
  ui: options.ui,
64
+ uiPath: options.uiPath,
55
65
  version: options.version
56
66
  });
57
67
  }
58
- function resolveSwaggerUiAssets(options) {
68
+ function resolveOpenApiRouteOptions(options) {
69
+ const normalizePath = path => `/${path.split('/').filter(Boolean).join('/')}`;
59
70
  return {
60
- cssUrl: options.swaggerUiAssets?.cssUrl ?? SWAGGER_UI_CSS_URL,
61
- jsBundleUrl: options.swaggerUiAssets?.jsBundleUrl ?? SWAGGER_UI_BUNDLE_JS_URL
71
+ documentPath: normalizePath(options.documentPath ?? DEFAULT_DOCUMENT_PATH),
72
+ uiPath: normalizePath(options.uiPath ?? DEFAULT_UI_PATH)
62
73
  };
63
74
  }
64
- function createSwaggerUiHtml(title, assets) {
65
- return `<!doctype html>
66
- <html lang="en">
67
- <head>
68
- <meta charset="utf-8" />
69
- <meta name="viewport" content="width=device-width, initial-scale=1" />
70
- <title>${escapeHtml(title)}</title>
71
- <link rel="stylesheet" href="${escapeHtml(assets.cssUrl)}" />
72
- </head>
73
- <body>
74
- <div id="swagger-ui"></div>
75
- <script src="${escapeHtml(assets.jsBundleUrl)}" crossorigin></script>
76
- <script>
77
- const specUrl = window.location.pathname.replace(/\/docs\/?$/, '/openapi.json');
78
- const swaggerUi = SwaggerUIBundle({
79
- url: specUrl,
80
- dom_id: '#swagger-ui'
81
- });
82
- void swaggerUi;
83
- </script>
84
- </body>
85
- </html>`;
86
- }
87
75
  function isOpenApiModuleOptions(value) {
88
76
  if (typeof value !== 'object' || value === null) {
89
77
  return false;
@@ -123,10 +111,11 @@ export class OpenApiModule {
123
111
  * ```
124
112
  */
125
113
  static forRoot(options) {
114
+ const snapshot = snapshotOpenApiModuleOptions(options);
126
115
  return this.createModule({
127
116
  scope: 'singleton',
128
- useValue: snapshotOpenApiModuleOptions(options)
129
- });
117
+ useValue: snapshot
118
+ }, resolveOpenApiRouteOptions(snapshot));
130
119
  }
131
120
 
132
121
  /**
@@ -138,7 +127,9 @@ export class OpenApiModule {
138
127
  * @example
139
128
  * ```ts
140
129
  * OpenApiModule.forRootAsync({
130
+ * documentPath: '/openapi/internal.json',
141
131
  * inject: [ConfigService],
132
+ * uiPath: '/docs/internal',
142
133
  * useFactory: (config) => ({
143
134
  * title: config.get('APP_NAME'),
144
135
  * version: config.get('APP_VERSION'),
@@ -147,13 +138,17 @@ export class OpenApiModule {
147
138
  * ```
148
139
  */
149
140
  static forRootAsync(options) {
141
+ const routes = resolveOpenApiRouteOptions(options);
150
142
  return this.createModule({
151
143
  inject: options.inject,
152
144
  scope: 'singleton',
153
- useFactory: async (...deps) => snapshotOpenApiModuleOptions(await options.useFactory(...deps))
154
- });
145
+ useFactory: async (...deps) => snapshotOpenApiModuleOptions({
146
+ ...(await options.useFactory(...deps)),
147
+ ...routes
148
+ })
149
+ }, routes);
155
150
  }
156
- static createModule(optionsProvider) {
151
+ static createModule(optionsProvider, routes) {
157
152
  let _initProto, _initClass;
158
153
  const openApiModuleOptionsToken = Symbol('fluo.openapi.module-options');
159
154
  const openApiDocumentToken = Symbol('fluo.openapi.document');
@@ -163,12 +158,12 @@ export class OpenApiModule {
163
158
  ({
164
159
  e: [_initProto],
165
160
  c: [_OpenApiController, _initClass]
166
- } = _applyDecs(this, [Controller(''), Inject(openApiDocumentToken, openApiModuleOptionsToken)], [[Get('/openapi.json'), 2, "getDocument"], [Get('/docs'), 2, "getSwaggerUi"]]));
161
+ } = _applyDecs(this, [Controller(''), Inject(openApiDocumentToken, openApiModuleOptionsToken)], [[Get(routes.documentPath), 2, "getDocument"], [Get(routes.uiPath), 2, "getSwaggerUi"]]));
167
162
  }
168
163
  constructor(document, options) {
164
+ _initProto(this);
169
165
  this.document = document;
170
166
  this.options = options;
171
- _initProto(this);
172
167
  }
173
168
  getDocument() {
174
169
  return cloneSnapshotValue(this.document);
@@ -178,7 +173,7 @@ export class OpenApiModule {
178
173
  throw new NotFoundException('Swagger UI is disabled.');
179
174
  }
180
175
  context.response.setHeader('content-type', 'text/html; charset=utf-8');
181
- return createSwaggerUiHtml(this.options.title, resolveSwaggerUiAssets(this.options));
176
+ return createSwaggerUiHtml(this.options.title, resolveSwaggerUiAssets(this.options.swaggerUiAssets), routes);
182
177
  }
183
178
  static {
184
179
  _initClass();
@@ -0,0 +1,62 @@
1
+ import type { OpenApiOperationObject, OpenApiParameterObject } from './schema-builder.js';
2
+ /** Standard OpenAPI 3.1 operation keys accepted on a Path Item Object. */
3
+ export type OpenApiOperationMethod = 'delete' | 'get' | 'head' | 'options' | 'patch' | 'post' | 'put' | 'trace';
4
+ interface OpenApiReferenceObject {
5
+ $ref: string;
6
+ summary?: string;
7
+ description?: string;
8
+ }
9
+ interface OpenApiServerObject {
10
+ url: string;
11
+ description?: string;
12
+ variables?: Record<string, {
13
+ default: string;
14
+ description?: string;
15
+ enum?: string[];
16
+ }>;
17
+ }
18
+ /**
19
+ * OpenAPI Path Item Object containing standard operations, fixed fields, and specification extensions.
20
+ */
21
+ export interface OpenApiPathItemObject {
22
+ $ref?: string;
23
+ summary?: string;
24
+ description?: string;
25
+ servers?: OpenApiServerObject[];
26
+ parameters?: (OpenApiParameterObject | OpenApiReferenceObject)[];
27
+ delete?: OpenApiOperationObject;
28
+ get?: OpenApiOperationObject;
29
+ head?: OpenApiOperationObject;
30
+ options?: OpenApiOperationObject;
31
+ patch?: OpenApiOperationObject;
32
+ post?: OpenApiOperationObject;
33
+ put?: OpenApiOperationObject;
34
+ trace?: OpenApiOperationObject;
35
+ [extension: `x-${string}`]: unknown;
36
+ }
37
+ /**
38
+ * Resolve one Fluo descriptor method to its standard OpenAPI operation key.
39
+ *
40
+ * @param method Descriptor method supplied by HTTP route metadata.
41
+ * @param path OpenAPI path used to identify invalid descriptor input.
42
+ * @returns The corresponding standard OpenAPI operation key.
43
+ * @throws {TypeError} When Fluo cannot author the descriptor method as an OpenAPI operation.
44
+ */
45
+ export declare function resolveDescriptorOperationMethod(method: string, path: string): OpenApiOperationMethod;
46
+ /**
47
+ * Determine whether a Path Item key is a standard OpenAPI 3.1 operation.
48
+ *
49
+ * @param key Path Item key to inspect.
50
+ * @returns Whether the key is a standard operation key.
51
+ */
52
+ export declare function isOpenApiOperationMethod(key: string): key is OpenApiOperationMethod;
53
+ /**
54
+ * Validate every transformed Path Item against the OpenAPI 3.1 key policy.
55
+ *
56
+ * @param paths Final document paths after any caller transform.
57
+ * @returns Nothing when every Path Item key is valid.
58
+ * @throws {TypeError} When a Path Item contains an unknown non-extension key.
59
+ */
60
+ export declare function validateOpenApiPathItemKeys(paths: Readonly<Record<string, object>>): void;
61
+ export {};
62
+ //# sourceMappingURL=path-item.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-item.d.ts","sourceRoot":"","sources":["../src/path-item.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE1F,0EAA0E;AAC1E,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;AAEhH,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,mBAAmB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,CAAC,sBAAsB,GAAG,sBAAsB,CAAC,EAAE,CAAC;IACjE,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC,GAAG,CAAC,EAAE,sBAAsB,CAAC;IAC7B,IAAI,CAAC,EAAE,sBAAsB,CAAC;IAC9B,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,IAAI,CAAC,EAAE,sBAAsB,CAAC;IAC9B,GAAG,CAAC,EAAE,sBAAsB,CAAC;IAC7B,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,CAAC,SAAS,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC;CACrC;AAyBD;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,sBAAsB,CAOrG;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,sBAAsB,CAEnF;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAUzF"}
@@ -0,0 +1,53 @@
1
+ /** Standard OpenAPI 3.1 operation keys accepted on a Path Item Object. */
2
+
3
+ /**
4
+ * OpenAPI Path Item Object containing standard operations, fixed fields, and specification extensions.
5
+ */
6
+
7
+ const OPENAPI_PATH_ITEM_FIXED_FIELDS = new Set(['$ref', 'description', 'parameters', 'servers', 'summary']);
8
+ const DESCRIPTOR_OPERATION_METHODS = new Map([['DELETE', 'delete'], ['GET', 'get'], ['HEAD', 'head'], ['OPTIONS', 'options'], ['PATCH', 'patch'], ['POST', 'post'], ['PUT', 'put'], ['TRACE', 'trace']]);
9
+ const OPENAPI_OPERATION_METHODS = new Set([...DESCRIPTOR_OPERATION_METHODS.values()]);
10
+
11
+ /**
12
+ * Resolve one Fluo descriptor method to its standard OpenAPI operation key.
13
+ *
14
+ * @param method Descriptor method supplied by HTTP route metadata.
15
+ * @param path OpenAPI path used to identify invalid descriptor input.
16
+ * @returns The corresponding standard OpenAPI operation key.
17
+ * @throws {TypeError} When Fluo cannot author the descriptor method as an OpenAPI operation.
18
+ */
19
+ export function resolveDescriptorOperationMethod(method, path) {
20
+ const operationMethod = DESCRIPTOR_OPERATION_METHODS.get(method);
21
+ if (operationMethod !== undefined) {
22
+ return operationMethod;
23
+ }
24
+ throw new TypeError(`OpenAPI cannot document unsupported HTTP method "${method}" for path "${path}".`);
25
+ }
26
+
27
+ /**
28
+ * Determine whether a Path Item key is a standard OpenAPI 3.1 operation.
29
+ *
30
+ * @param key Path Item key to inspect.
31
+ * @returns Whether the key is a standard operation key.
32
+ */
33
+ export function isOpenApiOperationMethod(key) {
34
+ return OPENAPI_OPERATION_METHODS.has(key);
35
+ }
36
+
37
+ /**
38
+ * Validate every transformed Path Item against the OpenAPI 3.1 key policy.
39
+ *
40
+ * @param paths Final document paths after any caller transform.
41
+ * @returns Nothing when every Path Item key is valid.
42
+ * @throws {TypeError} When a Path Item contains an unknown non-extension key.
43
+ */
44
+ export function validateOpenApiPathItemKeys(paths) {
45
+ for (const [path, pathItem] of Object.entries(paths)) {
46
+ for (const key of Object.keys(pathItem)) {
47
+ if (isOpenApiOperationMethod(key) || OPENAPI_PATH_ITEM_FIXED_FIELDS.has(key) || key.startsWith('x-')) {
48
+ continue;
49
+ }
50
+ throw new TypeError(`OpenAPI Path Item for path "${path}" contains unsupported key "${key}".`);
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,9 @@
1
+ import type { OpenApiDocument } from './schema-builder.js';
2
+ /**
3
+ * Normalize legacy exclusive-bound and nullable metadata before an OpenAPI 3.1 document is exposed.
4
+ *
5
+ * @param document Generated document, including any final caller transform.
6
+ * @returns A detached document whose schema keywords use OpenAPI 3.1 forms.
7
+ */
8
+ export declare function normalizeOpenApiDocumentSchemaBounds(document: OpenApiDocument): OpenApiDocument;
9
+ //# sourceMappingURL=schema-bounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-bounds.d.ts","sourceRoot":"","sources":["../src/schema-bounds.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EAMhB,MAAM,qBAAqB,CAAC;AAuP7B;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CAiB/F"}
@@ -0,0 +1,175 @@
1
+ import { isOpenApiOperationMethod } from './path-item.js';
2
+ function normalizeSchemaRecord(schemas, path, normalizedSchemas) {
3
+ const normalized = {};
4
+ for (const [name, schema] of Object.entries(schemas)) {
5
+ normalized[name] = normalizeOpenApiSchemaBounds(schema, `${path}.${name}`, normalizedSchemas);
6
+ }
7
+ return normalized;
8
+ }
9
+ function normalizeSchemaList(schemas, path, normalizedSchemas) {
10
+ return schemas.map((schema, index) => normalizeOpenApiSchemaBounds(schema, `${path}[${String(index)}]`, normalizedSchemas));
11
+ }
12
+ function normalizeOpenApiSchemaBounds(schema, path, normalizedSchemas) {
13
+ const cachedSchema = normalizedSchemas.get(schema);
14
+ if (cachedSchema) {
15
+ return cachedSchema;
16
+ }
17
+ if (schema.nullable === true && schema.type === undefined) {
18
+ const nullableUnion = {};
19
+ const nonNullableSchema = {
20
+ ...schema
21
+ };
22
+ delete nonNullableSchema.nullable;
23
+ normalizedSchemas.set(schema, nullableUnion);
24
+ nullableUnion.anyOf = [normalizeOpenApiSchemaBounds(nonNullableSchema, `${path}.anyOf[0]`, normalizedSchemas), {
25
+ type: 'null'
26
+ }];
27
+ return nullableUnion;
28
+ }
29
+ const normalized = {
30
+ ...schema
31
+ };
32
+ normalizedSchemas.set(schema, normalized);
33
+ if (typeof schema.nullable === 'boolean') {
34
+ delete normalized.nullable;
35
+ }
36
+ if (schema.nullable === true && schema.type !== undefined) {
37
+ normalized.type = typeof schema.type === 'string' ? schema.type === 'null' ? 'null' : [schema.type, 'null'] : schema.type.includes('null') ? schema.type : [...schema.type, 'null'];
38
+ }
39
+ if (typeof schema.exclusiveMinimum === 'number' && !Number.isFinite(schema.exclusiveMinimum)) {
40
+ throw new TypeError(`OpenAPI schema ${path}.exclusiveMinimum must be a finite number.`);
41
+ }
42
+ if (typeof schema.exclusiveMinimum === 'boolean') {
43
+ delete normalized.exclusiveMinimum;
44
+ if (schema.exclusiveMinimum) {
45
+ if (schema.minimum === undefined || !Number.isFinite(schema.minimum)) {
46
+ throw new TypeError(`OpenAPI schema ${path}.exclusiveMinimum requires a finite minimum.`);
47
+ }
48
+ normalized.exclusiveMinimum = schema.minimum;
49
+ delete normalized.minimum;
50
+ }
51
+ }
52
+ if (typeof schema.exclusiveMaximum === 'number' && !Number.isFinite(schema.exclusiveMaximum)) {
53
+ throw new TypeError(`OpenAPI schema ${path}.exclusiveMaximum must be a finite number.`);
54
+ }
55
+ if (typeof schema.exclusiveMaximum === 'boolean') {
56
+ delete normalized.exclusiveMaximum;
57
+ if (schema.exclusiveMaximum) {
58
+ if (schema.maximum === undefined || !Number.isFinite(schema.maximum)) {
59
+ throw new TypeError(`OpenAPI schema ${path}.exclusiveMaximum requires a finite maximum.`);
60
+ }
61
+ normalized.exclusiveMaximum = schema.maximum;
62
+ delete normalized.maximum;
63
+ }
64
+ }
65
+ if (schema.allOf) {
66
+ normalized.allOf = normalizeSchemaList(schema.allOf, `${path}.allOf`, normalizedSchemas);
67
+ }
68
+ if (schema.oneOf) {
69
+ normalized.oneOf = normalizeSchemaList(schema.oneOf, `${path}.oneOf`, normalizedSchemas);
70
+ }
71
+ if (schema.anyOf) {
72
+ normalized.anyOf = normalizeSchemaList(schema.anyOf, `${path}.anyOf`, normalizedSchemas);
73
+ }
74
+ if (schema.not) {
75
+ normalized.not = normalizeOpenApiSchemaBounds(schema.not, `${path}.not`, normalizedSchemas);
76
+ }
77
+ if (schema.properties) {
78
+ normalized.properties = normalizeSchemaRecord(schema.properties, `${path}.properties`, normalizedSchemas);
79
+ }
80
+ if (schema.items) {
81
+ normalized.items = normalizeOpenApiSchemaBounds(schema.items, `${path}.items`, normalizedSchemas);
82
+ }
83
+ if (typeof schema.additionalProperties === 'object') {
84
+ normalized.additionalProperties = normalizeOpenApiSchemaBounds(schema.additionalProperties, `${path}.additionalProperties`, normalizedSchemas);
85
+ }
86
+ return normalized;
87
+ }
88
+ function normalizeContent(content, path, normalizedSchemas) {
89
+ const normalized = {};
90
+ for (const [mediaType, media] of Object.entries(content)) {
91
+ normalized[mediaType] = {
92
+ ...media,
93
+ schema: normalizeOpenApiSchemaBounds(media.schema, `${path}.${mediaType}.schema`, normalizedSchemas)
94
+ };
95
+ }
96
+ return normalized;
97
+ }
98
+ function normalizeResponses(responses, path, normalizedSchemas) {
99
+ const normalized = {};
100
+ for (const [status, response] of Object.entries(responses)) {
101
+ normalized[status] = {
102
+ ...response,
103
+ ...(response.content ? {
104
+ content: normalizeContent(response.content, `${path}.${status}.content`, normalizedSchemas)
105
+ } : {})
106
+ };
107
+ }
108
+ return normalized;
109
+ }
110
+ function normalizeOperation(operation, path, normalizedSchemas) {
111
+ return {
112
+ ...operation,
113
+ ...(operation.parameters ? {
114
+ parameters: operation.parameters.map((parameter, index) => ({
115
+ ...parameter,
116
+ schema: normalizeOpenApiSchemaBounds(parameter.schema, `${path}.parameters[${String(index)}].schema`, normalizedSchemas)
117
+ }))
118
+ } : {}),
119
+ ...(operation.requestBody ? {
120
+ requestBody: {
121
+ ...operation.requestBody,
122
+ content: normalizeContent(operation.requestBody.content, `${path}.requestBody.content`, normalizedSchemas)
123
+ }
124
+ } : {}),
125
+ responses: normalizeResponses(operation.responses, `${path}.responses`, normalizedSchemas)
126
+ };
127
+ }
128
+ function normalizePaths(paths, normalizedSchemas) {
129
+ const normalizedPaths = {};
130
+ for (const [path, pathItem] of Object.entries(paths)) {
131
+ const normalizedPathItem = {};
132
+ if (pathItem.parameters) {
133
+ normalizedPathItem.parameters = pathItem.parameters.map((parameter, index) => 'schema' in parameter ? {
134
+ ...parameter,
135
+ schema: normalizeOpenApiSchemaBounds(parameter.schema, `paths.${path}.parameters[${String(index)}].schema`, normalizedSchemas)
136
+ } : {
137
+ ...parameter
138
+ });
139
+ }
140
+ for (const [key, value] of Object.entries(pathItem)) {
141
+ if (key === 'parameters') {
142
+ continue;
143
+ }
144
+ if (isOpenApiOperationMethod(key)) {
145
+ normalizedPathItem[key] = value ? normalizeOperation(value, `paths.${path}.${key}`, normalizedSchemas) : undefined;
146
+ continue;
147
+ }
148
+ Reflect.set(normalizedPathItem, key, value);
149
+ }
150
+ normalizedPaths[path] = normalizedPathItem;
151
+ }
152
+ return normalizedPaths;
153
+ }
154
+
155
+ /**
156
+ * Normalize legacy exclusive-bound and nullable metadata before an OpenAPI 3.1 document is exposed.
157
+ *
158
+ * @param document Generated document, including any final caller transform.
159
+ * @returns A detached document whose schema keywords use OpenAPI 3.1 forms.
160
+ */
161
+ export function normalizeOpenApiDocumentSchemaBounds(document) {
162
+ const normalizedSchemas = new WeakMap();
163
+ return {
164
+ ...document,
165
+ ...(document.components ? {
166
+ components: {
167
+ ...document.components,
168
+ ...(document.components.schemas ? {
169
+ schemas: normalizeSchemaRecord(document.components.schemas, 'components.schemas', normalizedSchemas)
170
+ } : {})
171
+ }
172
+ } : {}),
173
+ paths: normalizePaths(document.paths, normalizedSchemas)
174
+ };
175
+ }
@@ -1,5 +1,7 @@
1
1
  import type { Constructor } from '@fluojs/core';
2
2
  import type { HandlerDescriptor } from '@fluojs/http';
3
+ import { type OpenApiPathItemObject } from './path-item.js';
4
+ export type { OpenApiPathItemObject } from './path-item.js';
3
5
  /**
4
6
  * JSON Schema primitive type names accepted by OpenAPI 3.1 schema objects.
5
7
  */
@@ -52,6 +54,7 @@ export interface OpenApiSchemaObject {
52
54
  deprecated?: boolean;
53
55
  readOnly?: boolean;
54
56
  writeOnly?: boolean;
57
+ /** Legacy compatibility input normalized to an OpenAPI 3.1 null union before emission. */
55
58
  nullable?: boolean;
56
59
  minimum?: number;
57
60
  maximum?: number;
@@ -147,12 +150,6 @@ export interface OpenApiOperationObject {
147
150
  requestBody?: OpenApiRequestBodyObject;
148
151
  security?: OpenApiSecurityRequirementObject[];
149
152
  }
150
- /**
151
- * OpenAPI path-item object containing one or more HTTP method operations.
152
- */
153
- export interface OpenApiPathItemObject {
154
- [method: string]: OpenApiOperationObject | undefined;
155
- }
156
153
  /**
157
154
  * Root OpenAPI 3.1.0 document produced by `buildOpenApiDocument(...)`.
158
155
  */
@@ -1 +1 @@
1
- {"version":3,"file":"schema-builder.d.ts","sourceRoot":"","sources":["../src/schema-builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema-builder.d.ts","sourceRoot":"","sources":["../src/schema-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,cAAc,CAAC;AAErE,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,cAAc,CAAC;AAQlE,OAAO,EAEL,KAAK,qBAAqB,EAG3B,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAK5D;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC/C,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,0BAA0B,GAAG,SAAS,0BAA0B,EAAE,CAAC;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC9B,GAAG,CAAC,EAAE,mBAAmB,CAAC;IAC1B,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAC;IACrD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,eAAe,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QACN,QAAQ,CAAC,EAAE;YACT,gBAAgB,EAAE,MAAM,CAAC;YACzB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SAChC,CAAC;QACF,QAAQ,CAAC,EAAE;YACT,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SAChC,CAAC;QACF,iBAAiB,CAAC,EAAE;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SAChC,CAAC;QACF,iBAAiB,CAAC,EAAE;YAClB,gBAAgB,EAAE,MAAM,CAAC;YACzB,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SAChC,CAAC;KACH,CAAC;IACF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;CAC/D;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,QAAQ,CAAC,EAAE,gCAAgC,EAAE,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IAC1D,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC9D,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,eAAe,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,GAAG,MAAM,CAAC;AAsiC5D;;;;;GAKG;AACH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,eAAe,CAqD1F"}
@@ -1,5 +1,7 @@
1
- import { getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/internal';
1
+ import { getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/request-pipeline';
2
2
  import { getControllerTags, getMethodApiMetadata } from './decorators.js';
3
+ import { resolveDescriptorOperationMethod, validateOpenApiPathItemKeys } from './path-item.js';
4
+ import { normalizeOpenApiDocumentSchemaBounds } from './schema-bounds.js';
3
5
  import { cloneSnapshotValue } from './snapshot.js';
4
6
 
5
7
  /**
@@ -46,10 +48,6 @@ import { cloneSnapshotValue } from './snapshot.js';
46
48
  * OpenAPI operation object emitted for a single HTTP method on a path.
47
49
  */
48
50
 
49
- /**
50
- * OpenAPI path-item object containing one or more HTTP method operations.
51
- */
52
-
53
51
  /**
54
52
  * Root OpenAPI 3.1.0 document produced by `buildOpenApiDocument(...)`.
55
53
  */
@@ -491,12 +489,9 @@ function ensureComponentSchema(dto, componentSchemas, context) {
491
489
  const schemaName = getDtoSchemaName(dto, context);
492
490
  return ensureComponentSchemaFromEntries(schemaName, collectDtoEntries(dto, context), componentSchemas, context);
493
491
  }
494
- function createParameters(dto, context) {
495
- if (!dto) {
496
- return [];
497
- }
498
- const entries = collectDtoEntries(dto, context).filter(entry => entry.binding?.metadata.source === 'path' || entry.binding?.metadata.source === 'query' || entry.binding?.metadata.source === 'header' || entry.binding?.metadata.source === 'cookie');
499
- return entries.map(entry => {
492
+ function createParameters(dto, routePath, context) {
493
+ const entries = dto ? collectDtoEntries(dto, context).filter(entry => entry.binding?.metadata.source === 'path' || entry.binding?.metadata.source === 'query' || entry.binding?.metadata.source === 'header' || entry.binding?.metadata.source === 'cookie') : [];
494
+ const parameters = entries.map(entry => {
500
495
  const source = entry.binding.metadata.source;
501
496
  const rules = entry.validation?.rules ?? [];
502
497
  const inferred = inferPrimitiveTypeFromRules(rules, context) ?? {};
@@ -509,6 +504,22 @@ function createParameters(dto, context) {
509
504
  schema
510
505
  };
511
506
  });
507
+ const documentedPathParameters = new Set(parameters.filter(parameter => parameter.in === 'path').map(parameter => parameter.name));
508
+ for (const match of routePath.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)) {
509
+ const name = match[1];
510
+ if (!documentedPathParameters.has(name)) {
511
+ documentedPathParameters.add(name);
512
+ parameters.push({
513
+ in: 'path',
514
+ name,
515
+ required: true,
516
+ schema: {
517
+ type: 'string'
518
+ }
519
+ });
520
+ }
521
+ }
522
+ return parameters;
512
523
  }
513
524
  function ensureErrorResponseSchema(componentSchemas) {
514
525
  const schemaName = 'ErrorResponse';
@@ -785,7 +796,7 @@ function hasBearerAuthRequirement(security) {
785
796
  return Boolean(security?.some(requirement => Object.keys(requirement).includes('bearerAuth')));
786
797
  }
787
798
  function createOperationObject(descriptor, methodMeta, responses, componentSchemas, security, context, usedOperationIds) {
788
- const parameters = mergeOperationParameters(createParameters(descriptor.route.request, context), methodMeta?.parameters);
799
+ const parameters = mergeOperationParameters(createParameters(descriptor.route.request, descriptor.route.path, context), methodMeta?.parameters);
789
800
  const requestBody = mergeOperationRequestBody(createRequestBody(descriptor.route.request, componentSchemas, context), methodMeta);
790
801
  return {
791
802
  operationId: resolveUniqueOperationId(normalizeOperationId(descriptor), usedOperationIds),
@@ -813,11 +824,11 @@ function createOperationObject(descriptor, methodMeta, responses, componentSchem
813
824
  }
814
825
  function buildOperationEntry(descriptor, componentSchemas, defaultErrorResponsesPolicy, context, usedOperationIds) {
815
826
  const openApiPath = expressPathToOpenApi(descriptor.route.path);
816
- const method = descriptor.route.method.toLowerCase();
817
827
  const methodMeta = getMethodApiMetadata(descriptor.controllerToken, descriptor.methodName);
818
828
  if (methodMeta?.excludeEndpoint === true) {
819
829
  return undefined;
820
830
  }
831
+ const method = resolveDescriptorOperationMethod(descriptor.route.method, openApiPath);
821
832
  const responses = createOperationResponses(descriptor, methodMeta, componentSchemas, defaultErrorResponsesPolicy, context);
822
833
  const security = createOperationSecurity(methodMeta);
823
834
  const operation = createOperationObject(descriptor, methodMeta, responses, componentSchemas, security, context, usedOperationIds);
@@ -917,5 +928,7 @@ export function buildOpenApiDocument(options) {
917
928
  openapi: '3.1.0',
918
929
  paths
919
930
  };
920
- return options.documentTransform ? options.documentTransform(document) : document;
931
+ const transformedDocument = options.documentTransform ? options.documentTransform(document) : document;
932
+ validateOpenApiPathItemKeys(transformedDocument.paths);
933
+ return normalizeOpenApiDocumentSchemaBounds(transformedDocument);
921
934
  }
@@ -0,0 +1,29 @@
1
+ type SwaggerUiRoutes = {
2
+ readonly documentPath: string;
3
+ readonly uiPath: string;
4
+ };
5
+ /**
6
+ * Asset URLs used by the generated Swagger UI HTML page.
7
+ */
8
+ export interface OpenApiSwaggerUiAssetsOptions {
9
+ cssUrl?: string;
10
+ jsBundleUrl?: string;
11
+ }
12
+ /**
13
+ * Resolve pinned or caller-provided Swagger UI asset URLs.
14
+ *
15
+ * @param assets Optional caller-provided asset URLs.
16
+ * @returns Complete asset URLs for generated Swagger UI HTML.
17
+ */
18
+ export declare function resolveSwaggerUiAssets(assets: OpenApiSwaggerUiAssetsOptions | undefined): Required<OpenApiSwaggerUiAssetsOptions>;
19
+ /**
20
+ * Render Swagger UI HTML for one configured OpenAPI document route.
21
+ *
22
+ * @param title Document title rendered into the page.
23
+ * @param assets Complete Swagger UI asset URLs.
24
+ * @param routes Normalized JSON document and UI routes.
25
+ * @returns A standalone Swagger UI HTML document.
26
+ */
27
+ export declare function createSwaggerUiHtml(title: string, assets: Required<OpenApiSwaggerUiAssetsOptions>, routes: SwaggerUiRoutes): string;
28
+ export {};
29
+ //# sourceMappingURL=swagger-ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swagger-ui.d.ts","sourceRoot":"","sources":["../src/swagger-ui.ts"],"names":[],"mappings":"AAKA,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,6BAA6B,GAAG,SAAS,GAChD,QAAQ,CAAC,6BAA6B,CAAC,CAKzC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,CAAC,6BAA6B,CAAC,EAC/C,MAAM,EAAE,eAAe,GACtB,MAAM,CA6BR"}