@fluojs/openapi 1.1.0 → 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.
package/README.ko.md CHANGED
@@ -20,6 +20,8 @@ fluo를 위한 descriptor-driven OpenAPI 3.1.0 문서 생성 패키지입니다.
20
20
  pnpm add @fluojs/openapi
21
21
  ```
22
22
 
23
+ `@fluojs/openapi`는 Node.js `>=24.0.0 <27`을 지원하며 `engines.node`로 정확히 이 범위를 선언합니다. 이 package-owned 지원 계약은 Node 24 미만과 Node 27 이상을 제외하며 portable `@fluojs/runtime`에는 package-wide Node engine이 없습니다. 이전 1.x 릴리스는 `engines.node >=20.0.0`을 광고했습니다.
24
+
23
25
  ## 사용 시점
24
26
 
25
27
  - **Swagger UI**를 사용하여 REST API에 대한 대화형 문서를 제공하고 싶을 때.
@@ -35,7 +37,7 @@ pnpm add @fluojs/openapi
35
37
  ```typescript
36
38
  import { Controller, Get } from '@fluojs/http';
37
39
  import { Module } from '@fluojs/core';
38
- import { bootstrapNodeApplication } from '@fluojs/runtime/node';
40
+ import { bootstrapNodeApplication } from '@fluojs/platform-nodejs';
39
41
  import { OpenApiModule, ApiOperation, ApiResponse, ApiTag } from '@fluojs/openapi';
40
42
 
41
43
  @ApiTag('Users')
@@ -74,9 +76,28 @@ await app.listen(3000);
74
76
 
75
77
  ## 핵심 기능
76
78
 
79
+ ### 빈 decorator options
80
+
81
+ `@ApiOperation()` / `@ApiOperation(undefined)`와 `@ApiBody()` / `@ApiBody(undefined)`는
82
+ 기존 `{}` 의미를 사용합니다. Summary, description, deprecated, required, schema를 임의로
83
+ 생성하지 않습니다. 빈 body metadata는 DTO에서 추론한 body를 유지하고, 추론한 body가 없으면
84
+ `requestBody`를 추가하지 않습니다. 빈 write도 stacking의 이전 metadata를 덮어쓸 수 있으므로
85
+ decorator 자체 생략과 항상 같지는 않습니다. 기존 적용 시 `null` 실패를 유지합니다.
86
+ `ApiTag(tag)`, `ApiResponse(status, options?)`, parameter/security name은 계속 필수이며
87
+ 지원하는 OpenAPI Path Item method 집합은 바뀌지 않습니다.
88
+
77
89
  ### 자동 명세 생성
78
90
  fluo는 `sources`와 `descriptors`로 전달된 controller 및 handler descriptor만 조사하여 OpenAPI 3.1.0 문서를 작성합니다. 이 명시적 입력 집합의 경로, 메서드, 파라미터, 요청 바디가 포함되며, controller를 application module에 import하는 것만으로는 자동 추가되지 않습니다.
79
91
 
92
+ ### OpenAPI 3.1 Path Item 검증
93
+ Builder는 표준 Path Item operation인 `get`, `put`, `post`, `delete`, `options`, `head`, `patch`, `trace`만 생성합니다. Fluo catch-all `ALL` descriptor는 runtime routing 입력이지 OpenAPI operation이 아니므로, 비표준 `all` key로 직렬화하지 않고 문서 생성을 거부합니다. 지원하지 않는 다른 descriptor method도 같은 path-specific error로 실패합니다.
94
+
95
+ `documentTransform` 이후에는 모든 Path Item을 다시 검증합니다. Transform은 OpenAPI 3.1 operation(`trace` 포함), fixed field(`$ref`, `summary`, `description`, `servers`, `parameters`), `x-*` specification extension을 사용할 수 있습니다. `all`, `query` 또는 기타 알 수 없는 key는 문서가 노출되기 전에 생성을 실패시킵니다.
96
+
97
+ ### 2.0으로 마이그레이션
98
+
99
+ 업그레이드하기 전에 모든 `@All()` route 또는 custom handler descriptor를 각각 지원되는 HTTP method route로 바꾸거나 OpenAPI 입력에서 제외하세요. `documentTransform`에서는 `all`, `query` 같은 비표준 Path Item key를 제거하고 표준 operation, fixed field, `x-*` extension만 유지하세요. 지원하지 않는 입력은 이제 잘못된 문서를 생성하는 대신 문서 생성 단계에서 예외를 발생시킵니다.
100
+
80
101
  ### 응답 미디어 타입
81
102
  HTTP 핸들러가 `@fluojs/http`의 `@Produces(...)`를 선언하면, 생성된 OpenAPI 응답은 해당 미디어 타입을 response `content` 키로 사용합니다. 예를 들어 `@ApiResponse(...)` 스키마가 있는 핸들러에 `@Produces('application/json', 'application/problem+json')`를 붙이면, `application/json`만으로 되돌아가지 않고 두 미디어 타입 모두 같은 응답 스키마로 방출합니다.
82
103
 
@@ -89,13 +110,19 @@ Builder는 handler 반환값이나 TypeScript 반환 타입을 검사해 respons
89
110
  ### 통합 DTO 스키마
90
111
  `@fluojs/validation`과 함께 DTO binding 및 validation metadata에서 request schema를 파생합니다. Response DTO는 `@ApiResponse(..., { type: ResponseDto })` 또는 `extraModels`처럼 명시적으로 참조할 때만 OpenAPI component가 됩니다.
91
112
 
113
+ ### OpenAPI 3.1 배타적 경계
114
+ `OpenApiSchemaObject`는 OpenAPI 3.1의 숫자 `exclusiveMinimum` 및 `exclusiveMaximum` 값을 받으면서 기존 boolean metadata와의 호환성도 유지합니다. `minimum` 또는 `maximum`과 함께 사용한 `true` 플래그는 생성 문서에서 대응하는 숫자 배타적 경계로 변환되고, `false` 플래그는 생략되는 대신 포괄 경계는 유지됩니다. 유한한 숫자 배타적 경계는 변경 없이 통과합니다. 유한한 대응 경계가 없는 `true` 플래그나 유한하지 않은 숫자 배타적 경계는 잘못된 OpenAPI 3.1 schema를 생성하는 대신 문서 생성을 실패시킵니다. 같은 정규화는 문서가 노출되기 전에 `documentTransform` 이후에도 실행됩니다.
115
+
116
+ ### OpenAPI 3.1 Nullable 스키마
117
+ `OpenApiSchemaObject`는 호환성 입력으로 legacy boolean `nullable` keyword를 계속 받지만 생성되는 OpenAPI 3.1 문서에는 이를 내보내지 않습니다. `nullable: true`는 scalar 및 array constraint를 보존하면서 선언된 `type` union에 `null`을 추가합니다. `$ref` schema를 포함해 `type`이 없는 schema는 `{ type: 'null' }`과의 `anyOf` union으로 변환됩니다. `nullable: false`는 schema를 바꾸지 않고 제거됩니다. 기존 null union에는 `null`을 중복 추가하지 않으며, 이 재귀 정규화는 `documentTransform` 뒤에도 실행됩니다.
118
+
92
119
  ### 버전 관리 지원
93
120
  `@fluojs/http`의 URI 기반 버전 관리를 자동으로 처리합니다. OpenAPI 경로에 해결된 버전 경로가 올바르게 반영됩니다.
94
121
 
95
122
  ### 보안 문서화
96
- `@ApiBearerAuth()` 및 `@ApiSecurity()`를 사용하여 Bearer 토큰이나 API 키와 같은 보안 요구사항을 쉽게 문서화할 수 있습니다.
123
+ `@ApiBearerAuth()` 및 `@ApiSecurity(name, scopes?)`를 사용하여 Bearer 토큰이나 API 키와 같은 보안 요구사항을 쉽게 문서화할 수 있습니다.
97
124
 
98
- 같은 scheme에 대해 여러 `@ApiSecurity()` 데코레이터를 쌓으면, 해당 scheme의 scope가 하나의 누적 OpenAPI security requirement로 병합됩니다. 따라서 라우트가 `['reports:read']`와 `['reports:write', 'reports:read']`처럼 겹치는 scope를 선언해도 OAuth 스타일 요구사항은 결정적으로 유지되며, 서로 다른 scheme은 별도 requirement로 남습니다.
125
+ 같은 scheme에 대해 여러 `@ApiSecurity(name, scopes?)` 데코레이터를 쌓으면, 해당 scheme의 scope가 하나의 누적 OpenAPI security requirement로 병합됩니다. 따라서 라우트가 `['reports:read']`와 `['reports:write', 'reports:read']`처럼 겹치는 scope를 선언해도 OAuth 스타일 요구사항은 결정적으로 유지되며, 서로 다른 scheme은 별도 requirement로 남습니다.
99
126
 
100
127
  ### 결정적인 Swagger UI 자산
101
128
  `ui: true`를 활성화하면 생성되는 `/docs` 페이지는 정확한 `swagger-ui-dist` 버전의 자산을 참조하여 패키지 릴리스마다 동일한 동작을 유지합니다. 오프라인 또는 CSP 제어 환경에서 자체 호스팅 자산이 필요하면 `swaggerUiAssets.cssUrl`과 `swaggerUiAssets.jsBundleUrl`을 설정하세요. 생성된 HTML은 해당 URL을 이스케이프하며 Swagger UI 인스턴스를 `window.ui`에 노출하지 않습니다.
@@ -135,6 +162,13 @@ Path는 `@fluojs/http` route grammar를 따르며 중복 slash와 trailing slash
135
162
  ### Async 등록과 옵션
136
163
  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`은 문서 생성 뒤 제공되기 전에 실행됩니다.
137
164
 
165
+ ### NestJS 마이그레이션 계약 차이
166
+ 생성 문서는 NestJS Swagger와 일대일 호환 계층이 아닙니다. fluo는 기본적으로 명시적으로 선언한 응답을 대체하지 않으면서 `400`, `401`, `403`, `404`, `500` 응답과 공용 `ErrorResponse` schema를 추가합니다. 클라이언트를 다시 생성하기 전에 생성된 error contract를 검증하고, legacy 문서에 이 기본 응답이 들어가면 안 되는 경우 `defaultErrorResponsesPolicy: 'omit'`을 설정하세요.
167
+
168
+ fluo는 controller tag, handler name, HTTP method, normalized path에서 각 `operationId`를 결정론적으로 만듭니다. 충돌에는 숫자 suffix가 붙습니다. 생성된 client가 legacy identifier에 의존한다면 문서를 제공하기 전에 `documentTransform`에서 생성된 operation ID를 변경하고, 변환된 문서를 client generator로 검증하세요.
169
+
170
+ `forRootAsync(...)`에서는 `documentPath`와 `uiPath`의 route가 `useFactory(...)`가 resolve되기 전에 compile되므로 두 값은 바깥 registration option입니다. 이 path들은 `inject`와 `useFactory` 옆에 두고 factory에서는 document configuration만 반환하세요. factory가 반환한 path로는 이미 등록된 route를 다시 구성할 수 없습니다.
171
+
138
172
  ## 공개 API
139
173
 
140
174
  - `OpenApiModule`: OpenAPI 통합을 위한 메인 엔트리 포인트.
@@ -142,12 +176,13 @@ title/version/source 설정이 DI나 async setup에서 나오는 경우 `OpenApi
142
176
  - `ApiBody`, `ApiParam`, `ApiQuery`, `ApiHeader`, `ApiCookie`: 이름이 겹칠 때 추론된 요청 문서를 대체하는 명시적 요청 본문 및 파라미터 문서화 데코레이터.
143
177
  - `ApiBearerAuth`, `ApiSecurity`: 보안 요구사항 데코레이터.
144
178
  - `ApiExcludeEndpoint`: 특정 핸들러를 문서화에서 제외.
179
+ - `ApiOperationOptions`, `ApiResponseOptions`, `ApiParameterOptions`, `ApiBodyOptions`: `@ApiOperation(...)`, `@ApiResponse(...)`, `@ApiParam(...)`, `@ApiQuery(...)`, `@ApiHeader(...)`, `@ApiCookie(...)`, `@ApiBody(...)`가 받는 데코레이터 옵션 타입.
145
180
  - `buildOpenApiDocument`: 프로그래밍 방식의 문서 빌더 (저수준).
146
181
  - `OpenApiHandlerRegistry`: 고급 통합에서 문서 생성 전에 handler descriptor를 스냅샷하는 mutable descriptor registry.
147
182
  - `getControllerTags`, `getMethodApiMetadata`: 고급 테스트와 통합 tooling을 위한 metadata reader.
148
183
  - `OpenApiModuleOptions`, `OpenApiAsyncModuleOptions`, `OpenApiRouteOptions`, `OpenApiSwaggerUiAssetsOptions`, `BuildOpenApiDocumentOptions`, `DefaultErrorResponsesPolicy`: module과 builder integration을 위한 option type.
149
184
  - `OpenApiDocument`, `OpenApiSecuritySchemeObject` 및 관련 OpenAPI shape type: 테스트, tooling, integration을 위한 typed document surface.
150
- - `OpenApiSchemaObject`: 명시적 `@ApiBody(...)` 및 `@ApiResponse(...)` 스키마를 위한 타입화된 스키마 표면입니다. OpenAPI 3.1 조합(`allOf`, `oneOf`, `anyOf`), 객체/배열 제약, examples/defaults, 읽기/쓰기/Deprecated 주석을 포함합니다.
185
+ - `OpenApiSchemaObject`: 명시적 `@ApiBody(...)` 및 `@ApiResponse(...)` 스키마를 위한 타입화된 스키마 표면입니다. OpenAPI 3.1 조합(`allOf`, `oneOf`, `anyOf`), legacy `nullable` 입력과 호환되는 null union, 객체/배열 제약, examples/defaults, 읽기/쓰기/Deprecated 주석을 포함합니다.
151
186
 
152
187
  ## 관련 패키지
153
188
 
package/README.md CHANGED
@@ -20,6 +20,8 @@ Descriptor-driven OpenAPI 3.1.0 document generation for fluo, with standard deco
20
20
  pnpm add @fluojs/openapi
21
21
  ```
22
22
 
23
+ `@fluojs/openapi` supports Node.js `>=24.0.0 <27` and declares that exact range through `engines.node`. That package-owned support contract excludes Node versions below 24 and Node 27+; portable `@fluojs/runtime` has no package-wide Node engine. Earlier 1.x releases advertised `engines.node >=20.0.0`.
24
+
23
25
  ## When to Use
24
26
 
25
27
  - When you want to provide interactive documentation for your REST API using **Swagger UI**.
@@ -35,7 +37,7 @@ Register the `OpenApiModule` and pass `sources`, prebuilt `descriptors`, or both
35
37
  ```typescript
36
38
  import { Controller, Get } from '@fluojs/http';
37
39
  import { Module } from '@fluojs/core';
38
- import { bootstrapNodeApplication } from '@fluojs/runtime/node';
40
+ import { bootstrapNodeApplication } from '@fluojs/platform-nodejs';
39
41
  import { OpenApiModule, ApiOperation, ApiResponse, ApiTag } from '@fluojs/openapi';
40
42
 
41
43
  @ApiTag('Users')
@@ -74,9 +76,28 @@ When a prebuilt descriptor and a discovered source resolve to the same OpenAPI p
74
76
 
75
77
  ## Core Capabilities
76
78
 
79
+ ### Empty decorator options
80
+
81
+ `@ApiOperation()` / `@ApiOperation(undefined)` and `@ApiBody()` / `@ApiBody(undefined)`
82
+ use the existing `{}` semantics. No summary, description, deprecated flag, required flag,
83
+ or schema is invented. Empty body metadata preserves a DTO-inferred body and adds no
84
+ `requestBody` when none is inferred. Empty writes can overwrite earlier stacked metadata,
85
+ so these calls are not always equivalent to omitting the decorator. Existing application-time
86
+ `null` failures remain. `ApiTag(tag)`, `ApiResponse(status, options?)`, and parameter/security
87
+ names remain required; the supported OpenAPI Path Item methods do not change.
88
+
77
89
  ### Automated Specification Generation
78
90
  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.
79
91
 
92
+ ### OpenAPI 3.1 Path Item Validation
93
+ The builder emits only standard Path Item operations: `get`, `put`, `post`, `delete`, `options`, `head`, `patch`, and `trace`. Fluo catch-all `ALL` descriptors are runtime routing inputs, not OpenAPI operations, so document generation rejects them instead of serializing a nonstandard `all` key. Unsupported descriptor methods fail with the same path-specific error.
94
+
95
+ After `documentTransform`, every Path Item is validated again. Transforms may use the OpenAPI 3.1 operations (including `trace`), fixed fields (`$ref`, `summary`, `description`, `servers`, and `parameters`), and `x-*` specification extensions. Keys such as `all`, `query`, or other unknown fields fail document generation before the document is exposed.
96
+
97
+ ### Migrating to 2.0
98
+
99
+ Before upgrading, replace every `@All()` route or custom handler descriptor with separate supported HTTP method routes, or exclude it from OpenAPI input. In `documentTransform`, remove nonstandard Path Item keys such as `all` and `query`; retain only standard operations, fixed fields, and `x-*` extensions. Unsupported input now throws during document generation instead of emitting an invalid document.
100
+
80
101
  ### Response Media Types
81
102
  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
103
 
@@ -89,13 +110,19 @@ The builder does not inspect handler return values or TypeScript return types to
89
110
  ### Integrated DTO Schemas
90
111
  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`.
91
112
 
113
+ ### OpenAPI 3.1 Exclusive Bounds
114
+ `OpenApiSchemaObject` accepts OpenAPI 3.1 numeric `exclusiveMinimum` and `exclusiveMaximum` values while retaining compatibility with legacy boolean metadata. A `true` flag paired with `minimum` or `maximum` becomes the corresponding numeric exclusive bound in the emitted document, and a `false` flag is omitted while its inclusive bound remains. Finite numeric exclusive bounds pass through unchanged. A `true` flag without a finite paired bound, or a non-finite numeric exclusive bound, fails document generation instead of emitting an invalid OpenAPI 3.1 schema. The same normalization runs after `documentTransform` before the document is exposed.
115
+
116
+ ### OpenAPI 3.1 Nullable Schemas
117
+ `OpenApiSchemaObject` continues to accept the legacy boolean `nullable` keyword as compatibility input, but generated OpenAPI 3.1 documents never emit it. `nullable: true` adds `null` to a declared `type` union while preserving scalar and array constraints; schemas without `type`, including `$ref` schemas, become an `anyOf` union with `{ type: 'null' }`. `nullable: false` is removed without changing the schema. Existing null unions are not duplicated, and the recursive normalization also runs after `documentTransform`.
118
+
92
119
  ### Versioning Support
93
120
  Handles URI-based versioning from `@fluojs/http` automatically. Your OpenAPI paths will correctly reflect the resolved versioned routes.
94
121
 
95
122
  ### Security Documentation
96
- Easily document authentication requirements like Bearer tokens or API keys using `@ApiBearerAuth()` and `@ApiSecurity()`.
123
+ Easily document authentication requirements like Bearer tokens or API keys using `@ApiBearerAuth()` and `@ApiSecurity(name, scopes?)`.
97
124
 
98
- Stacking multiple `@ApiSecurity()` decorators for the same scheme merges scopes into one cumulative OpenAPI security requirement for that scheme. This keeps OAuth-style requirements deterministic when a route declares overlapping scopes such as `['reports:read']` and `['reports:write', 'reports:read']`, while different schemes remain separate requirements.
125
+ Stacking multiple `@ApiSecurity(name, scopes?)` decorators for the same scheme merges scopes into one cumulative OpenAPI security requirement for that scheme. This keeps OAuth-style requirements deterministic when a route declares overlapping scopes such as `['reports:read']` and `['reports:write', 'reports:read']`, while different schemes remain separate requirements.
99
126
 
100
127
  ### Deterministic Swagger UI Assets
101
128
  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`.
@@ -135,6 +162,13 @@ Paths follow the `@fluojs/http` route grammar and normalize duplicate or trailin
135
162
  ### Async Registration and Options
136
163
  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.
137
164
 
165
+ ### NestJS Migration Contract Differences
166
+ Generated documents are not a one-to-one NestJS Swagger compatibility layer. By default, fluo adds `400`, `401`, `403`, `404`, and `500` responses that do not replace explicitly declared responses, and includes the shared `ErrorResponse` schema. Verify the generated error contract before regenerating clients; set `defaultErrorResponsesPolicy: 'omit'` when the legacy document must not receive those default responses.
167
+
168
+ Fluo derives each `operationId` deterministically from the controller tag, handler name, HTTP method, and normalized path. Collisions receive numeric suffixes. If generated clients rely on legacy identifiers, rename the generated operation IDs in `documentTransform` before the document is served, then verify the transformed document with the client generator.
169
+
170
+ With `forRootAsync(...)`, `documentPath` and `uiPath` are outer registration options because their routes are compiled before `useFactory(...)` resolves. Keep those paths beside `inject` and `useFactory`; return only document configuration from the factory. A path returned by the factory cannot reconfigure the already-registered routes.
171
+
138
172
  ## Public API
139
173
 
140
174
  - `OpenApiModule`: Main entry point for OpenAPI integration.
@@ -142,12 +176,13 @@ Use `OpenApiModule.forRootAsync(...)` when title/version/source configuration co
142
176
  - `ApiBody`, `ApiParam`, `ApiQuery`, `ApiHeader`, `ApiCookie`: Explicit request-body and parameter documentation decorators that override inferred request documentation when names overlap.
143
177
  - `ApiBearerAuth`, `ApiSecurity`: Security requirement decorators.
144
178
  - `ApiExcludeEndpoint`: Omit specific handlers from documentation.
179
+ - `ApiOperationOptions`, `ApiResponseOptions`, `ApiParameterOptions`, `ApiBodyOptions`: Decorator option types accepted by `@ApiOperation(...)`, `@ApiResponse(...)`, `@ApiParam(...)`, `@ApiQuery(...)`, `@ApiHeader(...)`, `@ApiCookie(...)`, and `@ApiBody(...)`.
145
180
  - `buildOpenApiDocument`: Programmatic document builder (low-level).
146
181
  - `OpenApiHandlerRegistry`: Mutable descriptor registry used by advanced integrations to snapshot handler descriptors before document generation.
147
182
  - `getControllerTags`, `getMethodApiMetadata`: Metadata readers for advanced tests and integration tooling.
148
183
  - `OpenApiModuleOptions`, `OpenApiAsyncModuleOptions`, `OpenApiRouteOptions`, `OpenApiSwaggerUiAssetsOptions`, `BuildOpenApiDocumentOptions`, `DefaultErrorResponsesPolicy`: Option types for module and builder integrations.
149
184
  - `OpenApiDocument`, `OpenApiSecuritySchemeObject`, and related OpenAPI shape types: Typed document surface for tests, tooling, and integrations.
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.
185
+ - `OpenApiSchemaObject`: Typed schema surface for explicit `@ApiBody(...)` and `@ApiResponse(...)` schemas, including OpenAPI 3.1 composition (`allOf`, `oneOf`, `anyOf`), null unions with legacy `nullable` input compatibility, object/array constraints, examples/defaults, and read/write/deprecated annotations.
151
186
 
152
187
  ## Related Packages
153
188
 
@@ -121,10 +121,11 @@ export declare function ApiTag(tag: string): ClassDecoratorFn;
121
121
  /**
122
122
  * Describe a controller method's OpenAPI operation metadata.
123
123
  *
124
- * @param options Operation metadata such as summary, description, and deprecation flag.
124
+ * @param options Operation metadata; omission or `undefined` uses `{}` without inventing operation fields.
125
125
  * @returns A method decorator that stores operation metadata.
126
+ * @remarks An empty write can overwrite earlier stacked operation metadata; it is not equivalent to omitting the decorator.
126
127
  */
127
- export declare function ApiOperation(options: ApiOperationOptions): MethodDecoratorFn;
128
+ export declare function ApiOperation(options?: ApiOperationOptions): MethodDecoratorFn;
128
129
  /**
129
130
  * Exclude a controller method from generated OpenAPI `paths`.
130
131
  *
@@ -174,10 +175,11 @@ export declare function ApiCookie(name: string, options?: ApiParameterOptions):
174
175
  /**
175
176
  * Declare an explicit request body for a controller method.
176
177
  *
177
- * @param options Request-body metadata and schema/content declarations.
178
+ * @param options Request-body metadata; omission or `undefined` uses `{}` without inventing a schema or required flag.
178
179
  * @returns A method decorator that stores request-body metadata.
180
+ * @remarks Empty metadata preserves DTO body inference and emits no body without inference. It can overwrite earlier stacked body metadata.
179
181
  */
180
- export declare function ApiBody(options: ApiBodyOptions): MethodDecoratorFn;
182
+ export declare function ApiBody(options?: ApiBodyOptions): MethodDecoratorFn;
181
183
  /**
182
184
  * Declare an expected HTTP response for a controller method.
183
185
  *
@@ -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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"}
@@ -186,10 +186,11 @@ export function ApiTag(tag) {
186
186
  /**
187
187
  * Describe a controller method's OpenAPI operation metadata.
188
188
  *
189
- * @param options Operation metadata such as summary, description, and deprecation flag.
189
+ * @param options Operation metadata; omission or `undefined` uses `{}` without inventing operation fields.
190
190
  * @returns A method decorator that stores operation metadata.
191
+ * @remarks An empty write can overwrite earlier stacked operation metadata; it is not equivalent to omitting the decorator.
191
192
  */
192
- export function ApiOperation(options) {
193
+ export function ApiOperation(options = {}) {
193
194
  return (_value, context) => {
194
195
  const bag = context.metadata;
195
196
  let map = bag[openApiMethodOperationKey];
@@ -336,10 +337,11 @@ export function ApiCookie(name, options = {}) {
336
337
  /**
337
338
  * Declare an explicit request body for a controller method.
338
339
  *
339
- * @param options Request-body metadata and schema/content declarations.
340
+ * @param options Request-body metadata; omission or `undefined` uses `{}` without inventing a schema or required flag.
340
341
  * @returns A method decorator that stores request-body metadata.
342
+ * @remarks Empty metadata preserves DTO body inference and emits no body without inference. It can overwrite earlier stacked body metadata.
341
343
  */
342
- export function ApiBody(options) {
344
+ export function ApiBody(options = {}) {
343
345
  return (_value, context) => {
344
346
  const bag = context.metadata;
345
347
  let map = bag[openApiMethodRequestBodyKey];
@@ -161,9 +161,9 @@ export class OpenApiModule {
161
161
  } = _applyDecs(this, [Controller(''), Inject(openApiDocumentToken, openApiModuleOptionsToken)], [[Get(routes.documentPath), 2, "getDocument"], [Get(routes.uiPath), 2, "getSwaggerUi"]]));
162
162
  }
163
163
  constructor(document, options) {
164
+ _initProto(this);
164
165
  this.document = document;
165
166
  this.options = options;
166
- _initProto(this);
167
167
  }
168
168
  getDocument() {
169
169
  return cloneSnapshotValue(this.document);
@@ -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":"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"}
@@ -1,5 +1,7 @@
1
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
  }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "documentation",
10
10
  "rest"
11
11
  ],
12
- "version": "1.1.0",
12
+ "version": "2.0.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "directory": "packages/openapi"
19
19
  },
20
20
  "engines": {
21
- "node": ">=20.0.0"
21
+ "node": ">=24.0.0 <27"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
@@ -36,13 +36,14 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.1.0",
40
- "@fluojs/http": "^2.0.1",
41
- "@fluojs/runtime": "^2.0.1",
42
- "@fluojs/validation": "^1.0.6"
39
+ "@fluojs/core": "^2.0.0",
40
+ "@fluojs/http": "^3.0.0",
41
+ "@fluojs/runtime": "^3.0.0"
43
42
  },
44
43
  "devDependencies": {
45
- "vitest": "^3.2.4"
44
+ "vitest": "^4.1.11",
45
+ "@fluojs/platform-nodejs": "^2.0.0",
46
+ "@fluojs/validation": "^2.0.0"
46
47
  },
47
48
  "scripts": {
48
49
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",