@fluojs/openapi 1.0.3 → 1.1.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.
package/README.ko.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
4
 
5
- fluo를 위한 데코레이터 기반 OpenAPI 3.1.0 문서 생성 패키지입니다. 별도의 수동 동기화 없이 API 문서를 자동으로 생성하고 제공하며, 선택적으로 Swagger UI를 지원합니다.
5
+ fluo를 위한 descriptor-driven OpenAPI 3.1.0 문서 생성 패키지입니다. 명시적 문서 metadata를 위한 standard decorator와 선택적 Swagger UI를 지원합니다.
6
6
 
7
7
  ## 목차
8
8
 
@@ -25,7 +25,8 @@ pnpm add @fluojs/openapi
25
25
  - **Swagger UI**를 사용하여 REST API에 대한 대화형 문서를 제공하고 싶을 때.
26
26
  - 클라이언트 생성 또는 테스트를 위해 기계 읽기 가능한 **OpenAPI 3.1.0** 명세가 필요할 때.
27
27
  - 표준 데코레이터를 사용하여 API 문서와 코드를 동기화된 상태로 유지하고 싶을 때.
28
- - DTO 검증 메타데이터를 사용하여 복잡한 요청/응답 모델을 문서화해야 할 때.
28
+ - DTO binding/validation metadata에서 request model을 파생하고 response model을 명시적으로 선언해야 할 때.
29
+ - 하나의 application에서 여러 API version 또는 audience를 위해 JSON/UI route를 분리해야 할 때.
29
30
 
30
31
  ## 빠른 시작
31
32
 
@@ -74,7 +75,7 @@ await app.listen(3000);
74
75
  ## 핵심 기능
75
76
 
76
77
  ### 자동 명세 생성
77
- fluo는 컨트롤러와 메서드를 조사하여 전체 OpenAPI 3.1.0 문서를 작성합니다. 여기에는 경로, 메서드, 파라미터 요청 바디가 포함됩니다.
78
+ fluo는 `sources`와 `descriptors`로 전달된 controller 및 handler descriptor만 조사하여 OpenAPI 3.1.0 문서를 작성합니다. 명시적 입력 집합의 경로, 메서드, 파라미터, 요청 바디가 포함되며, controller를 application module에 import하는 것만으로는 자동 추가되지 않습니다.
78
79
 
79
80
  ### 응답 미디어 타입
80
81
  HTTP 핸들러가 `@fluojs/http`의 `@Produces(...)`를 선언하면, 생성된 OpenAPI 응답은 해당 미디어 타입을 response `content` 키로 사용합니다. 예를 들어 `@ApiResponse(...)` 스키마가 있는 핸들러에 `@Produces('application/json', 'application/problem+json')`를 붙이면, `application/json`만으로 되돌아가지 않고 두 미디어 타입 모두 같은 응답 스키마로 방출합니다.
@@ -82,8 +83,11 @@ HTTP 핸들러가 `@fluojs/http`의 `@Produces(...)`를 선언하면, 생성된
82
83
  ### 기본 성공 응답
83
84
  핸들러가 `@ApiResponse(...)` 또는 `@HttpCode(...)`를 선언하지 않으면 OpenAPI builder는 메서드만 기준으로 한 암묵적 기본값을 적용합니다. `POST` 핸들러는 기본적으로 `201`, 그 밖의 메서드는 `200`을 사용합니다. `DELETE`와 `OPTIONS`처럼 본문이 없거나 런타임 결과에 따라 달라질 수 있는 경우에는 `@HttpCode(...)` 또는 `@ApiResponse(...)`로 의도한 성공 상태를 명시하세요.
84
85
 
86
+ ### 응답 문서화 경계
87
+ Builder는 handler 반환값이나 TypeScript 반환 타입을 검사해 response content를 추론하지 않습니다. 기본 success response에는 status와 `OK` description만 포함됩니다. OpenAPI 문서에 response body를 설명해야 하면 `@ApiResponse(...)`에 `schema` 또는 `type`을 추가하세요. 둘 중 어느 것도 없으면 명시적 response도 status와 description만 포함합니다.
88
+
85
89
  ### 통합 DTO 스키마
86
- `@fluojs/validation`과 원활하게 작동합니다. DTO 클래스는 자동으로 OpenAPI 컴포넌트로 변환되어 적절한 오퍼레이션에서 참조됩니다.
90
+ `@fluojs/validation`과 함께 DTO binding 및 validation metadata에서 request schema를 파생합니다. Response DTO `@ApiResponse(..., { type: ResponseDto })` 또는 `extraModels`처럼 명시적으로 참조할 때만 OpenAPI component가 됩니다.
87
91
 
88
92
  ### 버전 관리 지원
89
93
  `@fluojs/http`의 URI 기반 버전 관리를 자동으로 처리합니다. OpenAPI 경로에 해결된 버전 경로가 올바르게 반영됩니다.
@@ -96,11 +100,40 @@ HTTP 핸들러가 `@fluojs/http`의 `@Produces(...)`를 선언하면, 생성된
96
100
  ### 결정적인 Swagger UI 자산
97
101
  `ui: true`를 활성화하면 생성되는 `/docs` 페이지는 정확한 `swagger-ui-dist` 버전의 자산을 참조하여 패키지 릴리스마다 동일한 동작을 유지합니다. 오프라인 또는 CSP 제어 환경에서 자체 호스팅 자산이 필요하면 `swaggerUiAssets.cssUrl`과 `swaggerUiAssets.jsBundleUrl`을 설정하세요. 생성된 HTML은 해당 URL을 이스케이프하며 Swagger UI 인스턴스를 `window.ui`에 노출하지 않습니다.
98
102
 
103
+ ### 설정 가능한 문서 라우트
104
+ 각 `OpenApiModule` 등록은 `documentPath`에서 JSON을 제공하고 `uiPath`에 Swagger UI route를 예약합니다. 기본값은 계속 `/openapi.json`과 `/docs`이므로 기존 application은 설정을 바꿀 필요가 없습니다. 하나의 application에서 여러 OpenAPI module을 import할 때는 두 path를 모두 지정하세요.
105
+
106
+ ```typescript
107
+ @Module({
108
+ imports: [
109
+ OpenApiModule.forRoot({
110
+ documentPath: '/openapi/public.json',
111
+ sources: [{ controllerToken: PublicController }],
112
+ title: 'Public API',
113
+ ui: true,
114
+ uiPath: '/docs/public',
115
+ version: '1.0.0',
116
+ }),
117
+ OpenApiModule.forRoot({
118
+ documentPath: '/openapi/admin.json',
119
+ sources: [{ controllerToken: AdminController }],
120
+ title: 'Admin API',
121
+ ui: true,
122
+ uiPath: '/docs/admin',
123
+ version: '1.0.0',
124
+ }),
125
+ ],
126
+ })
127
+ class AppModule {}
128
+ ```
129
+
130
+ Path는 `@fluojs/http` route grammar를 따르며 중복 slash와 trailing slash를 정규화합니다. Route collision에는 document descriptor precedence가 적용되지 않습니다. JSON/UI path끼리, 서로 다른 OpenAPI module끼리, 또는 다른 application controller와 정규화된 `GET` route가 겹치면 application bootstrap이 `RouteConflictError`로 실패합니다. `ui`가 false여도 UI route는 예약되므로, 설정한 endpoint는 문서화된 `Swagger UI is disabled.` not-found response를 반환할 수 있습니다.
131
+
99
132
  ### 모듈 옵션 결정성
100
- `OpenApiModule.forRoot(...)`는 등록 시점에 옵션을 스냅샷하고 freeze합니다. 등록 후 원본 options 객체, `sources`, `descriptors`, `securitySchemes`, `extraModels`, `swaggerUiAssets`를 변경해도 제공되는 OpenAPI 문서나 `/docs` HTML은 바뀌지 않습니다. 생성된 singleton 문서도 defensive copy로 제공되므로 downstream response serialization이나 테스트가 이후 요청에 쓰이는 저장 문서를 변경할 수 없습니다. `OpenApiModule.forRootAsync(...)`도 async factory가 resolve된 뒤 같은 스냅샷을 적용하며, factory 실패는 bootstrap 중 전파됩니다.
133
+ `OpenApiModule.forRoot(...)`는 등록 시점에 옵션을 스냅샷하고 freeze합니다. 등록 후 원본 options 객체, `documentPath`, `uiPath`, `sources`, `descriptors`, `securitySchemes`, `extraModels`, `swaggerUiAssets`를 변경해도 제공되는 OpenAPI 문서나 UI HTML은 바뀌지 않습니다. 생성된 singleton 문서도 defensive copy로 제공되므로 downstream response serialization이나 테스트가 이후 요청에 쓰이는 저장 문서를 변경할 수 없습니다. `OpenApiModule.forRootAsync(...)`는 module compile 전에 outer registration의 `documentPath`와 `uiPath`를 고정하고, async document-options factory가 resolve된 뒤 같은 스냅샷을 적용하며, factory 실패는 bootstrap 중 전파됩니다.
101
134
 
102
135
  ### Async 등록과 옵션
103
- title/version/source 설정이 DI나 async setup에서 나오는 경우 `OpenApiModule.forRootAsync(...)`를 사용합니다. Module option에는 `sources`, `descriptors`, `securitySchemes`, `extraModels`, `defaultErrorResponsesPolicy`, `documentTransform`, `ui`, `swaggerUiAssets`가 포함됩니다. `defaultErrorResponsesPolicy`는 기본적으로 표준 error response와 `ErrorResponse` schema를 주입하며, `documentTransform`은 문서 생성 뒤 제공되기 전에 실행됩니다.
136
+ title/version/source 설정이 DI나 async setup에서 나오는 경우 `OpenApiModule.forRootAsync(...)`를 사용합니다. 등록 시점의 `documentPath`와 `uiPath`는 `inject`, `useFactory` 옆에 두고, factory에서는 `sources`, `descriptors`, `securitySchemes`, `extraModels`, `defaultErrorResponsesPolicy`, `documentTransform`, `ui`, `swaggerUiAssets`를 반환합니다. `defaultErrorResponsesPolicy`는 기본적으로 표준 error response와 `ErrorResponse` schema를 주입하며, `documentTransform`은 문서 생성 뒤 제공되기 전에 실행됩니다.
104
137
 
105
138
  ## 공개 API
106
139
 
@@ -112,7 +145,7 @@ title/version/source 설정이 DI나 async setup에서 나오는 경우 `OpenApi
112
145
  - `buildOpenApiDocument`: 프로그래밍 방식의 문서 빌더 (저수준).
113
146
  - `OpenApiHandlerRegistry`: 고급 통합에서 문서 생성 전에 handler descriptor를 스냅샷하는 mutable descriptor registry.
114
147
  - `getControllerTags`, `getMethodApiMetadata`: 고급 테스트와 통합 tooling을 위한 metadata reader.
115
- - `OpenApiModuleOptions`, `OpenApiSwaggerUiAssetsOptions`, `BuildOpenApiDocumentOptions`, `DefaultErrorResponsesPolicy`: module과 builder integration을 위한 option type.
148
+ - `OpenApiModuleOptions`, `OpenApiAsyncModuleOptions`, `OpenApiRouteOptions`, `OpenApiSwaggerUiAssetsOptions`, `BuildOpenApiDocumentOptions`, `DefaultErrorResponsesPolicy`: module과 builder integration을 위한 option type.
116
149
  - `OpenApiDocument`, `OpenApiSecuritySchemeObject` 및 관련 OpenAPI shape type: 테스트, tooling, integration을 위한 typed document surface.
117
150
  - `OpenApiSchemaObject`: 명시적 `@ApiBody(...)` 및 `@ApiResponse(...)` 스키마를 위한 타입화된 스키마 표면입니다. OpenAPI 3.1 조합(`allOf`, `oneOf`, `anyOf`), 객체/배열 제약, examples/defaults, 읽기/쓰기/Deprecated 주석을 포함합니다.
118
151
 
@@ -125,4 +158,5 @@ title/version/source 설정이 DI나 async setup에서 나오는 경우 `OpenApi
125
158
  ## 예제 소스
126
159
 
127
160
  - `packages/openapi/src/openapi-module.test.ts`: 통합 테스트 및 사용 예제.
161
+ - `packages/openapi/src/openapi-module-routes.test.ts`: 기본/custom/multi-document/route-collision 예제.
128
162
  - `packages/openapi/src/schema-builder.test.ts`: 문서 builder와 schema generation 예제.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
4
 
5
- Decorator-based OpenAPI 3.1.0 document generation for fluo. Automatically generate and serve your API documentation with zero manual synchronization and optional Swagger UI support.
5
+ Descriptor-driven OpenAPI 3.1.0 document generation for fluo, with standard decorators for explicit documentation metadata and optional Swagger UI support.
6
6
 
7
7
  ## Table of Contents
8
8
 
@@ -25,7 +25,8 @@ pnpm add @fluojs/openapi
25
25
  - When you want to provide interactive documentation for your REST API using **Swagger UI**.
26
26
  - When you need a machine-readable **OpenAPI 3.1.0** specification for client generation or testing.
27
27
  - When you want to keep your API documentation in sync with your code using standard decorators.
28
- - When you need to document complex request/response models using DTOs and validation metadata.
28
+ - When you need to derive request models from DTO binding/validation metadata and declare response models explicitly.
29
+ - When one application needs separate JSON and UI routes for multiple API versions or audiences.
29
30
 
30
31
  ## Quick Start
31
32
 
@@ -74,7 +75,7 @@ When a prebuilt descriptor and a discovered source resolve to the same OpenAPI p
74
75
  ## Core Capabilities
75
76
 
76
77
  ### Automated Specification Generation
77
- fluo inspects your controllers and methods to build a complete OpenAPI 3.1.0 document. This includes paths, methods, parameters, and request bodies.
78
+ fluo inspects only the controllers and handler descriptors supplied through `sources` and `descriptors` to build an OpenAPI 3.1.0 document. This includes paths, methods, parameters, and request bodies for that explicit input set; importing a controller into an application module does not add it automatically.
78
79
 
79
80
  ### Response Media Types
80
81
  When an HTTP handler declares `@Produces(...)` from `@fluojs/http`, generated OpenAPI responses use those media types as the response `content` keys. For example, `@Produces('application/json', 'application/problem+json')` on a handler with an `@ApiResponse(...)` schema emits both media types with the same response schema instead of silently falling back to only `application/json`.
@@ -82,8 +83,11 @@ When an HTTP handler declares `@Produces(...)` from `@fluojs/http`, generated Op
82
83
  ### Default Success Responses
83
84
  When a handler does not declare `@ApiResponse(...)` or `@HttpCode(...)`, the OpenAPI builder applies method-only implicit defaults: `POST` handlers default to `201`, and other methods default to `200`. Bodyless or runtime-dependent cases such as `DELETE` and `OPTIONS` should declare the intended success status explicitly with `@HttpCode(...)` or `@ApiResponse(...)`.
84
85
 
86
+ ### Response Documentation Boundary
87
+ The builder does not inspect handler return values or TypeScript return types to infer response content. A default success response contains only its status and the description `OK`. Add `@ApiResponse(...)` with `schema` or `type` when the OpenAPI document must describe a response body; without either field, an explicit response still contains status and description only.
88
+
85
89
  ### Integrated DTO Schemas
86
- Works seamlessly with `@fluojs/validation`. Your DTO classes are automatically converted to OpenAPI components and referenced in the appropriate operations.
90
+ Works with `@fluojs/validation` to derive request schemas from DTO binding and validation metadata. Response DTOs become OpenAPI components only when they are referenced explicitly, such as with `@ApiResponse(..., { type: ResponseDto })` or `extraModels`.
87
91
 
88
92
  ### Versioning Support
89
93
  Handles URI-based versioning from `@fluojs/http` automatically. Your OpenAPI paths will correctly reflect the resolved versioned routes.
@@ -96,11 +100,40 @@ Stacking multiple `@ApiSecurity()` decorators for the same scheme merges scopes
96
100
  ### Deterministic Swagger UI Assets
97
101
  When `ui: true` is enabled, the generated `/docs` page references an exact `swagger-ui-dist` asset version so release behavior stays deterministic across package updates. If your deployment requires self-hosted assets for offline or CSP-controlled environments, set `swaggerUiAssets.cssUrl` and `swaggerUiAssets.jsBundleUrl`; the generated HTML escapes those URLs and does not expose the Swagger UI instance on `window.ui`.
98
102
 
103
+ ### Configurable Document Routes
104
+ Each `OpenApiModule` registration serves JSON at `documentPath` and reserves its Swagger UI route at `uiPath`. The defaults remain `/openapi.json` and `/docs`, so existing applications do not need configuration changes. Set both paths when one application imports multiple OpenAPI modules:
105
+
106
+ ```typescript
107
+ @Module({
108
+ imports: [
109
+ OpenApiModule.forRoot({
110
+ documentPath: '/openapi/public.json',
111
+ sources: [{ controllerToken: PublicController }],
112
+ title: 'Public API',
113
+ ui: true,
114
+ uiPath: '/docs/public',
115
+ version: '1.0.0',
116
+ }),
117
+ OpenApiModule.forRoot({
118
+ documentPath: '/openapi/admin.json',
119
+ sources: [{ controllerToken: AdminController }],
120
+ title: 'Admin API',
121
+ ui: true,
122
+ uiPath: '/docs/admin',
123
+ version: '1.0.0',
124
+ }),
125
+ ],
126
+ })
127
+ class AppModule {}
128
+ ```
129
+
130
+ Paths follow the `@fluojs/http` route grammar and normalize duplicate or trailing slashes. Route collisions do not use document-descriptor precedence: if two normalized `GET` routes collide—between JSON and UI paths, separate OpenAPI modules, or another application controller—application bootstrap fails with `RouteConflictError`. The UI route remains reserved when `ui` is false so the configured endpoint can return the documented `Swagger UI is disabled.` not-found response.
131
+
99
132
  ### Module Option Determinism
100
- `OpenApiModule.forRoot(...)` snapshots and freezes its options at registration time. Mutating the original options object, `sources`, `descriptors`, `securitySchemes`, `extraModels`, or `swaggerUiAssets` after registration does not alter the served OpenAPI document or `/docs` HTML. The generated singleton document is also served through defensive copies, so downstream response serialization or tests cannot mutate the stored document for later requests. `OpenApiModule.forRootAsync(...)` applies the same snapshot once the async factory resolves, and factory failures propagate during bootstrap.
133
+ `OpenApiModule.forRoot(...)` snapshots and freezes its options at registration time. Mutating the original options object, `documentPath`, `uiPath`, `sources`, `descriptors`, `securitySchemes`, `extraModels`, or `swaggerUiAssets` after registration does not alter the served OpenAPI document or UI HTML. The generated singleton document is also served through defensive copies, so downstream response serialization or tests cannot mutate the stored document for later requests. `OpenApiModule.forRootAsync(...)` fixes `documentPath` and `uiPath` from the outer registration before module compilation, applies the same snapshot once the async document-options factory resolves, and propagates factory failures during bootstrap.
101
134
 
102
135
  ### Async Registration and Options
103
- Use `OpenApiModule.forRootAsync(...)` when title/version/source configuration comes from DI or async setup. Module options include `sources`, `descriptors`, `securitySchemes`, `extraModels`, `defaultErrorResponsesPolicy`, `documentTransform`, `ui`, and `swaggerUiAssets`. `defaultErrorResponsesPolicy` defaults to injecting standard error responses and an `ErrorResponse` schema, while `documentTransform` runs after document generation and before serving.
136
+ Use `OpenApiModule.forRootAsync(...)` when title/version/source configuration comes from DI or async setup. Put registration-time `documentPath` and `uiPath` beside `inject` and `useFactory`; return `sources`, `descriptors`, `securitySchemes`, `extraModels`, `defaultErrorResponsesPolicy`, `documentTransform`, `ui`, and `swaggerUiAssets` from the factory. `defaultErrorResponsesPolicy` defaults to injecting standard error responses and an `ErrorResponse` schema, while `documentTransform` runs after document generation and before serving.
104
137
 
105
138
  ## Public API
106
139
 
@@ -112,7 +145,7 @@ Use `OpenApiModule.forRootAsync(...)` when title/version/source configuration co
112
145
  - `buildOpenApiDocument`: Programmatic document builder (low-level).
113
146
  - `OpenApiHandlerRegistry`: Mutable descriptor registry used by advanced integrations to snapshot handler descriptors before document generation.
114
147
  - `getControllerTags`, `getMethodApiMetadata`: Metadata readers for advanced tests and integration tooling.
115
- - `OpenApiModuleOptions`, `OpenApiSwaggerUiAssetsOptions`, `BuildOpenApiDocumentOptions`, `DefaultErrorResponsesPolicy`: Option types for module and builder integrations.
148
+ - `OpenApiModuleOptions`, `OpenApiAsyncModuleOptions`, `OpenApiRouteOptions`, `OpenApiSwaggerUiAssetsOptions`, `BuildOpenApiDocumentOptions`, `DefaultErrorResponsesPolicy`: Option types for module and builder integrations.
116
149
  - `OpenApiDocument`, `OpenApiSecuritySchemeObject`, and related OpenAPI shape types: Typed document surface for tests, tooling, and integrations.
117
150
  - `OpenApiSchemaObject`: Typed schema surface for explicit `@ApiBody(...)` and `@ApiResponse(...)` schemas, including OpenAPI 3.1 composition (`allOf`, `oneOf`, `anyOf`), object/array constraints, examples/defaults, and read/write/deprecated annotations.
118
151
 
@@ -125,4 +158,5 @@ Use `OpenApiModule.forRootAsync(...)` when title/version/source configuration co
125
158
  ## Example Sources
126
159
 
127
160
  - `packages/openapi/src/openapi-module.test.ts`: Integration tests and usage examples.
161
+ - `packages/openapi/src/openapi-module-routes.test.ts`: Default, custom, multi-document, and route-collision examples.
128
162
  - `packages/openapi/src/schema-builder.test.ts`: Document builder and schema generation examples.
@@ -1,4 +1,4 @@
1
- import { type Constructor, type MetadataPropertyKey } from '@fluojs/core';
1
+ import type { Constructor, MetadataPropertyKey } from '@fluojs/core';
2
2
  import type { OpenApiSchemaObject } from './schema-builder.js';
3
3
  /**
4
4
  * User-facing operation metadata accepted by `@ApiOperation(...)`.
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ensureMetadataSymbol, getStandardMetadataBag } from '@fluojs/core/internal';
1
+ import { ensureRequestPipelineMetadataSymbol, getRequestPipelineMetadataBag } from '@fluojs/core/request-pipeline';
2
2
 
3
3
  /**
4
4
  * User-facing operation metadata accepted by `@ApiOperation(...)`.
@@ -48,9 +48,9 @@ const openApiMethodRequestBodyKey = Symbol.for('fluo.openapi.method-request-body
48
48
  const openApiMethodSecurityKey = Symbol.for('fluo.openapi.method-security');
49
49
  const openApiMethodSecurityRequirementsKey = Symbol.for('fluo.openapi.method-security-requirements');
50
50
  const openApiMethodExcludeEndpointKey = Symbol.for('fluo.openapi.method-exclude-endpoint');
51
- ensureMetadataSymbol();
51
+ ensureRequestPipelineMetadataSymbol();
52
52
  function getMetadataBag(target) {
53
- return getStandardMetadataBag(target);
53
+ return getRequestPipelineMetadataBag(target);
54
54
  }
55
55
  function cloneUnknown(value) {
56
56
  if (value === null || value === undefined) {
@@ -1,22 +1,30 @@
1
- import { type HandlerDescriptor, type HandlerSource } from '@fluojs/http';
2
1
  import { type AsyncModuleOptions, type Constructor } from '@fluojs/core';
2
+ import { type HandlerDescriptor, type HandlerSource } from '@fluojs/http';
3
3
  import { type ModuleType } from '@fluojs/runtime';
4
4
  import { type DefaultErrorResponsesPolicy, type OpenApiDocument, type OpenApiSecuritySchemeObject } from './schema-builder.js';
5
+ import { type OpenApiSwaggerUiAssetsOptions } from './swagger-ui.js';
6
+ export type { OpenApiSwaggerUiAssetsOptions } from './swagger-ui.js';
5
7
  /**
6
- * Asset URLs used by the generated Swagger UI HTML page.
8
+ * Routes owned by one `OpenApiModule` registration.
9
+ *
10
+ * @remarks
11
+ * Paths use the normal `@fluojs/http` route grammar and are normalized before
12
+ * registration. The defaults remain `/openapi.json` and `/docs`.
7
13
  */
8
- export interface OpenApiSwaggerUiAssetsOptions {
9
- cssUrl?: string;
10
- jsBundleUrl?: string;
14
+ export interface OpenApiRouteOptions {
15
+ /** JSON document route. Defaults to `/openapi.json`. */
16
+ readonly documentPath?: string;
17
+ /** Swagger UI route. Defaults to `/docs`. */
18
+ readonly uiPath?: string;
11
19
  }
12
20
  /**
13
- * Public options for `OpenApiModule.forRoot(...)` and `OpenApiModule.forRootAsync(...)`.
21
+ * Public document and route options for `OpenApiModule.forRoot(...)`.
14
22
  *
15
23
  * @remarks
16
24
  * Keep README examples for full controller/module workflows. These options are
17
25
  * intended to document the runtime hooks that shape the generated document.
18
26
  */
19
- export interface OpenApiModuleOptions {
27
+ export interface OpenApiModuleOptions extends OpenApiRouteOptions {
20
28
  defaultErrorResponsesPolicy?: DefaultErrorResponsesPolicy;
21
29
  title: string;
22
30
  version: string;
@@ -28,6 +36,14 @@ export interface OpenApiModuleOptions {
28
36
  extraModels?: Constructor[];
29
37
  documentTransform?: (document: OpenApiDocument) => OpenApiDocument;
30
38
  }
39
+ /**
40
+ * Async OpenAPI registration options with routes fixed before module compilation.
41
+ *
42
+ * @remarks
43
+ * `documentPath` and `uiPath` belong to the outer registration because HTTP
44
+ * routes are compiled before the injected options factory resolves.
45
+ */
46
+ export type OpenApiAsyncModuleOptions = AsyncModuleOptions<Omit<OpenApiModuleOptions, keyof OpenApiRouteOptions>> & OpenApiRouteOptions;
31
47
  /**
32
48
  * Runtime module entrypoint for serving OpenAPI JSON and optional Swagger UI.
33
49
  */
@@ -57,7 +73,9 @@ export declare class OpenApiModule {
57
73
  * @example
58
74
  * ```ts
59
75
  * OpenApiModule.forRootAsync({
76
+ * documentPath: '/openapi/internal.json',
60
77
  * inject: [ConfigService],
78
+ * uiPath: '/docs/internal',
61
79
  * useFactory: (config) => ({
62
80
  * title: config.get('APP_NAME'),
63
81
  * version: config.get('APP_VERSION'),
@@ -65,7 +83,7 @@ export declare class OpenApiModule {
65
83
  * });
66
84
  * ```
67
85
  */
68
- static forRootAsync(options: AsyncModuleOptions<OpenApiModuleOptions>): ModuleType;
86
+ static forRootAsync(options: OpenApiAsyncModuleOptions): ModuleType;
69
87
  private static createModule;
70
88
  }
71
89
  //# sourceMappingURL=openapi-module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-module.d.ts","sourceRoot":"","sources":["../src/openapi-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAEnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAU,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAA0C,MAAM,cAAc,CAAC;AACzH,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,eAAe,EACpB,KAAK,2BAA2B,EACjC,MAAM,qBAAqB,CAAC;AAQ7B;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC9D,eAAe,CAAC,EAAE,6BAA6B,CAAC;IAChD,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,eAAe,CAAC;CACpE;AA2GD;;GAEG;AACH,qBAAa,aAAa;IACxB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,UAAU;IAOzD;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,oBAAoB,CAAC,GAAG,UAAU;IAQlF,OAAO,CAAC,MAAM,CAAC,YAAY;CAqE5B"}
1
+ {"version":3,"file":"openapi-module.d.ts","sourceRoot":"","sources":["../src/openapi-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAkD,MAAM,cAAc,CAAC;AACzH,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAGnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,eAAe,EACpB,KAAK,2BAA2B,EACjC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAEL,KAAK,6BAA6B,EAEnC,MAAM,iBAAiB,CAAC;AAKzB,YAAY,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC3C,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC9D,eAAe,CAAC,EAAE,6BAA6B,CAAC;IAChD,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,eAAe,CAAC;CACpE;AAED;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CACxD,IAAI,CAAC,oBAAoB,EAAE,MAAM,mBAAmB,CAAC,CACtD,GAAG,mBAAmB,CAAC;AAgFxB;;GAEG;AACH,qBAAa,aAAa;IACxB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,UAAU;IASzD;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU;IAanE,OAAO,CAAC,MAAM,CAAC,YAAY;CAwE5B"}
@@ -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,7 +158,7 @@ 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) {
169
164
  this.document = 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();
@@ -1,4 +1,4 @@
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
3
  import { cloneSnapshotValue } from './snapshot.js';
4
4
 
@@ -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"}
@@ -0,0 +1,67 @@
1
+ const SWAGGER_UI_DIST_VERSION = '5.32.2';
2
+ const SWAGGER_UI_DIST_BASE_URL = `https://unpkg.com/swagger-ui-dist@${SWAGGER_UI_DIST_VERSION}`;
3
+ const SWAGGER_UI_CSS_URL = `${SWAGGER_UI_DIST_BASE_URL}/swagger-ui.css`;
4
+ const SWAGGER_UI_BUNDLE_JS_URL = `${SWAGGER_UI_DIST_BASE_URL}/swagger-ui-bundle.js`;
5
+
6
+ /**
7
+ * Asset URLs used by the generated Swagger UI HTML page.
8
+ */
9
+
10
+ function escapeHtml(value) {
11
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;');
12
+ }
13
+ function escapeRegularExpression(value) {
14
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+ }
16
+ function serializeInlineScriptString(value) {
17
+ return JSON.stringify(value).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026');
18
+ }
19
+
20
+ /**
21
+ * Resolve pinned or caller-provided Swagger UI asset URLs.
22
+ *
23
+ * @param assets Optional caller-provided asset URLs.
24
+ * @returns Complete asset URLs for generated Swagger UI HTML.
25
+ */
26
+ export function resolveSwaggerUiAssets(assets) {
27
+ return {
28
+ cssUrl: assets?.cssUrl ?? SWAGGER_UI_CSS_URL,
29
+ jsBundleUrl: assets?.jsBundleUrl ?? SWAGGER_UI_BUNDLE_JS_URL
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Render Swagger UI HTML for one configured OpenAPI document route.
35
+ *
36
+ * @param title Document title rendered into the page.
37
+ * @param assets Complete Swagger UI asset URLs.
38
+ * @param routes Normalized JSON document and UI routes.
39
+ * @returns A standalone Swagger UI HTML document.
40
+ */
41
+ export function createSwaggerUiHtml(title, assets, routes) {
42
+ const uiPathPattern = routes.uiPath === '/' ? '/?$' : `${escapeRegularExpression(routes.uiPath)}/?$`;
43
+ return `<!doctype html>
44
+ <html lang="en">
45
+ <head>
46
+ <meta charset="utf-8" />
47
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
48
+ <title>${escapeHtml(title)}</title>
49
+ <link rel="stylesheet" href="${escapeHtml(assets.cssUrl)}" />
50
+ </head>
51
+ <body>
52
+ <div id="swagger-ui"></div>
53
+ <script src="${escapeHtml(assets.jsBundleUrl)}" crossorigin></script>
54
+ <script>
55
+ const specUrl = window.location.pathname.replace(
56
+ new RegExp(${serializeInlineScriptString(uiPathPattern)}),
57
+ () => ${serializeInlineScriptString(routes.documentPath)}
58
+ );
59
+ const swaggerUi = SwaggerUIBundle({
60
+ url: specUrl,
61
+ dom_id: '#swagger-ui'
62
+ });
63
+ void swaggerUi;
64
+ </script>
65
+ </body>
66
+ </html>`;
67
+ }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "documentation",
10
10
  "rest"
11
11
  ],
12
- "version": "1.0.3",
12
+ "version": "1.1.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/http": "^1.1.0",
40
- "@fluojs/validation": "^1.0.4",
41
- "@fluojs/core": "^1.0.3",
42
- "@fluojs/runtime": "^1.1.1"
39
+ "@fluojs/core": "^1.1.0",
40
+ "@fluojs/http": "^2.0.1",
41
+ "@fluojs/runtime": "^2.0.1",
42
+ "@fluojs/validation": "^1.0.6"
43
43
  },
44
44
  "devDependencies": {
45
45
  "vitest": "^3.2.4"