@navios/openapi 0.7.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.
Files changed (87) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +260 -0
  3. package/dist/src/__tests__/decorators.spec.d.mts +2 -0
  4. package/dist/src/__tests__/decorators.spec.d.mts.map +1 -0
  5. package/dist/src/__tests__/metadata.spec.d.mts +2 -0
  6. package/dist/src/__tests__/metadata.spec.d.mts.map +1 -0
  7. package/dist/src/__tests__/services.spec.d.mts +2 -0
  8. package/dist/src/__tests__/services.spec.d.mts.map +1 -0
  9. package/dist/src/decorators/api-deprecated.decorator.d.mts +33 -0
  10. package/dist/src/decorators/api-deprecated.decorator.d.mts.map +1 -0
  11. package/dist/src/decorators/api-exclude.decorator.d.mts +26 -0
  12. package/dist/src/decorators/api-exclude.decorator.d.mts.map +1 -0
  13. package/dist/src/decorators/api-operation.decorator.d.mts +58 -0
  14. package/dist/src/decorators/api-operation.decorator.d.mts.map +1 -0
  15. package/dist/src/decorators/api-security.decorator.d.mts +36 -0
  16. package/dist/src/decorators/api-security.decorator.d.mts.map +1 -0
  17. package/dist/src/decorators/api-stream.decorator.d.mts +50 -0
  18. package/dist/src/decorators/api-stream.decorator.d.mts.map +1 -0
  19. package/dist/src/decorators/api-summary.decorator.d.mts +24 -0
  20. package/dist/src/decorators/api-summary.decorator.d.mts.map +1 -0
  21. package/dist/src/decorators/api-tag.decorator.d.mts +42 -0
  22. package/dist/src/decorators/api-tag.decorator.d.mts.map +1 -0
  23. package/dist/src/decorators/index.d.mts +8 -0
  24. package/dist/src/decorators/index.d.mts.map +1 -0
  25. package/dist/src/index.d.mts +5 -0
  26. package/dist/src/index.d.mts.map +1 -0
  27. package/dist/src/metadata/index.d.mts +2 -0
  28. package/dist/src/metadata/index.d.mts.map +1 -0
  29. package/dist/src/metadata/openapi.metadata.d.mts +30 -0
  30. package/dist/src/metadata/openapi.metadata.d.mts.map +1 -0
  31. package/dist/src/services/endpoint-scanner.service.d.mts +42 -0
  32. package/dist/src/services/endpoint-scanner.service.d.mts.map +1 -0
  33. package/dist/src/services/index.d.mts +6 -0
  34. package/dist/src/services/index.d.mts.map +1 -0
  35. package/dist/src/services/metadata-extractor.service.d.mts +19 -0
  36. package/dist/src/services/metadata-extractor.service.d.mts.map +1 -0
  37. package/dist/src/services/openapi-generator.service.d.mts +91 -0
  38. package/dist/src/services/openapi-generator.service.d.mts.map +1 -0
  39. package/dist/src/services/path-builder.service.d.mts +73 -0
  40. package/dist/src/services/path-builder.service.d.mts.map +1 -0
  41. package/dist/src/services/schema-converter.service.d.mts +57 -0
  42. package/dist/src/services/schema-converter.service.d.mts.map +1 -0
  43. package/dist/src/tokens/index.d.mts +18 -0
  44. package/dist/src/tokens/index.d.mts.map +1 -0
  45. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  46. package/dist/tsconfig.spec.tsbuildinfo +1 -0
  47. package/dist/tsconfig.tsbuildinfo +1 -0
  48. package/dist/tsdown.config.d.mts +3 -0
  49. package/dist/tsdown.config.d.mts.map +1 -0
  50. package/dist/vitest.config.d.mts +3 -0
  51. package/dist/vitest.config.d.mts.map +1 -0
  52. package/lib/index.cjs +2120 -0
  53. package/lib/index.cjs.map +1 -0
  54. package/lib/index.d.cts +594 -0
  55. package/lib/index.d.cts.map +1 -0
  56. package/lib/index.d.mts +594 -0
  57. package/lib/index.d.mts.map +1 -0
  58. package/lib/index.mjs +2077 -0
  59. package/lib/index.mjs.map +1 -0
  60. package/package.json +44 -0
  61. package/project.json +66 -0
  62. package/src/__tests__/decorators.spec.mts +185 -0
  63. package/src/__tests__/metadata.spec.mts +207 -0
  64. package/src/__tests__/services.spec.mts +216 -0
  65. package/src/decorators/api-deprecated.decorator.mts +45 -0
  66. package/src/decorators/api-exclude.decorator.mts +29 -0
  67. package/src/decorators/api-operation.decorator.mts +59 -0
  68. package/src/decorators/api-security.decorator.mts +44 -0
  69. package/src/decorators/api-stream.decorator.mts +55 -0
  70. package/src/decorators/api-summary.decorator.mts +33 -0
  71. package/src/decorators/api-tag.decorator.mts +51 -0
  72. package/src/decorators/index.mts +7 -0
  73. package/src/index.mts +42 -0
  74. package/src/metadata/index.mts +2 -0
  75. package/src/metadata/openapi.metadata.mts +30 -0
  76. package/src/services/endpoint-scanner.service.mts +118 -0
  77. package/src/services/index.mts +21 -0
  78. package/src/services/metadata-extractor.service.mts +91 -0
  79. package/src/services/openapi-generator.service.mts +219 -0
  80. package/src/services/path-builder.service.mts +344 -0
  81. package/src/services/schema-converter.service.mts +96 -0
  82. package/src/tokens/index.mts +24 -0
  83. package/tsconfig.json +24 -0
  84. package/tsconfig.lib.json +8 -0
  85. package/tsconfig.spec.json +12 -0
  86. package/tsdown.config.mts +35 -0
  87. package/vitest.config.mts +11 -0
@@ -0,0 +1,118 @@
1
+ import type {
2
+ ControllerMetadata,
3
+ HandlerMetadata,
4
+ ModuleMetadata,
5
+ } from '@navios/core'
6
+ import type { BaseEndpointConfig } from '@navios/builder'
7
+
8
+ import { extractControllerMetadata, inject, Injectable, Logger } from '@navios/core'
9
+
10
+ import type { OpenApiEndpointMetadata } from '../metadata/openapi.metadata.mjs'
11
+
12
+ import { MetadataExtractorService } from './metadata-extractor.service.mjs'
13
+
14
+ /**
15
+ * Represents a discovered endpoint with all its metadata
16
+ */
17
+ export interface DiscoveredEndpoint {
18
+ /** Module metadata */
19
+ module: ModuleMetadata
20
+ /** Controller class */
21
+ controllerClass: any
22
+ /** Controller metadata */
23
+ controller: ControllerMetadata
24
+ /** Handler (endpoint) metadata */
25
+ handler: HandlerMetadata<any>
26
+ /** Endpoint configuration from @navios/builder */
27
+ config: BaseEndpointConfig
28
+ /** Extracted OpenAPI metadata */
29
+ openApiMetadata: OpenApiEndpointMetadata
30
+ }
31
+
32
+ /**
33
+ * Service responsible for scanning modules and discovering endpoints.
34
+ *
35
+ * Iterates through all modules, controllers, and endpoints,
36
+ * extracting OpenAPI metadata from decorators.
37
+ */
38
+ @Injectable()
39
+ export class EndpointScannerService {
40
+ private readonly logger = inject(Logger, {
41
+ context: EndpointScannerService.name,
42
+ })
43
+
44
+ private readonly metadataExtractor = inject(MetadataExtractorService)
45
+
46
+ /**
47
+ * Scans all loaded modules and discovers endpoints.
48
+ *
49
+ * @param modules - Map of loaded modules from NaviosApplication
50
+ * @returns Array of discovered endpoints
51
+ */
52
+ scan(modules: Map<string, ModuleMetadata>): DiscoveredEndpoint[] {
53
+ const endpoints: DiscoveredEndpoint[] = []
54
+
55
+ for (const [moduleName, moduleMetadata] of modules) {
56
+ if (!moduleMetadata.controllers || moduleMetadata.controllers.size === 0) {
57
+ continue
58
+ }
59
+
60
+ this.logger.debug(`Scanning module: ${moduleName}`)
61
+
62
+ for (const controllerClass of moduleMetadata.controllers) {
63
+ const controllerMeta = extractControllerMetadata(controllerClass)
64
+ const controllerEndpoints = this.scanController(
65
+ moduleMetadata,
66
+ controllerClass,
67
+ controllerMeta,
68
+ )
69
+ endpoints.push(...controllerEndpoints)
70
+ }
71
+ }
72
+
73
+ this.logger.debug(`Discovered ${endpoints.length} endpoints`)
74
+ return endpoints
75
+ }
76
+
77
+ /**
78
+ * Scans a controller and returns its endpoints
79
+ */
80
+ private scanController(
81
+ module: ModuleMetadata,
82
+ controllerClass: any,
83
+ controllerMeta: ControllerMetadata,
84
+ ): DiscoveredEndpoint[] {
85
+ const endpoints: DiscoveredEndpoint[] = []
86
+
87
+ for (const handler of controllerMeta.endpoints) {
88
+ // Skip endpoints without config (non-builder endpoints)
89
+ if (!handler.config) {
90
+ continue
91
+ }
92
+
93
+ const openApiMetadata = this.metadataExtractor.extract(
94
+ controllerMeta,
95
+ handler,
96
+ )
97
+
98
+ // Skip excluded endpoints
99
+ if (openApiMetadata.excluded) {
100
+ this.logger.debug(
101
+ `Skipping excluded endpoint: ${handler.classMethod}`,
102
+ )
103
+ continue
104
+ }
105
+
106
+ endpoints.push({
107
+ module,
108
+ controllerClass,
109
+ controller: controllerMeta,
110
+ handler,
111
+ config: handler.config as BaseEndpointConfig,
112
+ openApiMetadata,
113
+ })
114
+ }
115
+
116
+ return endpoints
117
+ }
118
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ EndpointScannerService,
3
+ type DiscoveredEndpoint,
4
+ } from './endpoint-scanner.service.mjs'
5
+
6
+ export { MetadataExtractorService } from './metadata-extractor.service.mjs'
7
+
8
+ export {
9
+ SchemaConverterService,
10
+ type SchemaConversionResult,
11
+ } from './schema-converter.service.mjs'
12
+
13
+ export {
14
+ PathBuilderService,
15
+ type PathItemResult,
16
+ } from './path-builder.service.mjs'
17
+
18
+ export {
19
+ OpenApiGeneratorService,
20
+ type OpenApiGeneratorOptions,
21
+ } from './openapi-generator.service.mjs'
@@ -0,0 +1,91 @@
1
+ import type { ControllerMetadata, HandlerMetadata } from '@navios/core'
2
+
3
+ import { Injectable } from '@navios/core'
4
+
5
+ import type { OpenApiEndpointMetadata } from '../metadata/openapi.metadata.mjs'
6
+
7
+ import {
8
+ ApiDeprecatedToken,
9
+ ApiExcludeToken,
10
+ ApiOperationToken,
11
+ ApiSecurityToken,
12
+ ApiStreamToken,
13
+ ApiSummaryToken,
14
+ ApiTagToken,
15
+ } from '../tokens/index.mjs'
16
+
17
+ /**
18
+ * Service responsible for extracting OpenAPI metadata from decorators.
19
+ *
20
+ * Merges controller-level and handler-level metadata to produce
21
+ * a complete OpenAPI metadata object for each endpoint.
22
+ */
23
+ @Injectable()
24
+ export class MetadataExtractorService {
25
+ /**
26
+ * Extracts and merges OpenAPI metadata from controller and handler.
27
+ *
28
+ * @param controller - Controller metadata
29
+ * @param handler - Handler metadata
30
+ * @returns Merged OpenAPI metadata
31
+ */
32
+ extract(
33
+ controller: ControllerMetadata,
34
+ handler: HandlerMetadata<any>,
35
+ ): OpenApiEndpointMetadata {
36
+ // Extract controller-level metadata
37
+ const controllerTag = controller.customAttributes.get(ApiTagToken) as
38
+ | { name: string; description?: string }
39
+ | undefined
40
+
41
+ // Extract handler-level metadata
42
+ const handlerTag = handler.customAttributes.get(ApiTagToken) as
43
+ | { name: string; description?: string }
44
+ | undefined
45
+ const operation = handler.customAttributes.get(ApiOperationToken) as
46
+ | {
47
+ summary?: string
48
+ description?: string
49
+ operationId?: string
50
+ deprecated?: boolean
51
+ externalDocs?: { url: string; description?: string }
52
+ }
53
+ | undefined
54
+ const summary = handler.customAttributes.get(ApiSummaryToken) as
55
+ | string
56
+ | undefined
57
+ const deprecated = handler.customAttributes.get(ApiDeprecatedToken) as
58
+ | { message?: string }
59
+ | undefined
60
+ const security = handler.customAttributes.get(ApiSecurityToken) as
61
+ | Record<string, string[]>
62
+ | undefined
63
+ const excluded = handler.customAttributes.get(ApiExcludeToken) as
64
+ | boolean
65
+ | undefined
66
+ const stream = handler.customAttributes.get(ApiStreamToken) as
67
+ | { contentType: string; description?: string }
68
+ | undefined
69
+
70
+ // Build tags array (handler tag takes precedence but both are included)
71
+ const tags: string[] = []
72
+ if (controllerTag?.name) {
73
+ tags.push(controllerTag.name)
74
+ }
75
+ if (handlerTag?.name && handlerTag.name !== controllerTag?.name) {
76
+ tags.push(handlerTag.name)
77
+ }
78
+
79
+ return {
80
+ tags,
81
+ summary: operation?.summary ?? summary,
82
+ description: operation?.description,
83
+ operationId: operation?.operationId,
84
+ deprecated: deprecated !== undefined || operation?.deprecated === true,
85
+ externalDocs: operation?.externalDocs,
86
+ security: security ? [security] : undefined,
87
+ excluded: excluded === true,
88
+ stream,
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,219 @@
1
+ import type { ModuleMetadata } from '@navios/core'
2
+ import type { oas31 } from 'zod-openapi'
3
+
4
+ import { inject, Injectable, Logger } from '@navios/core'
5
+
6
+ import type { DiscoveredEndpoint } from './endpoint-scanner.service.mjs'
7
+
8
+ import { EndpointScannerService } from './endpoint-scanner.service.mjs'
9
+ import { PathBuilderService } from './path-builder.service.mjs'
10
+
11
+ type OpenAPIObject = oas31.OpenAPIObject
12
+ type PathsObject = oas31.PathsObject
13
+ type SecuritySchemeObject = oas31.SecuritySchemeObject
14
+ type TagObject = oas31.TagObject
15
+
16
+ /**
17
+ * Options for generating the OpenAPI document
18
+ */
19
+ export interface OpenApiGeneratorOptions {
20
+ /**
21
+ * OpenAPI document info
22
+ */
23
+ info: {
24
+ title: string
25
+ version: string
26
+ description?: string
27
+ termsOfService?: string
28
+ contact?: {
29
+ name?: string
30
+ url?: string
31
+ email?: string
32
+ }
33
+ license?: {
34
+ name: string
35
+ url?: string
36
+ }
37
+ }
38
+
39
+ /**
40
+ * External documentation
41
+ */
42
+ externalDocs?: {
43
+ url: string
44
+ description?: string
45
+ }
46
+
47
+ /**
48
+ * Server definitions
49
+ */
50
+ servers?: Array<{
51
+ url: string
52
+ description?: string
53
+ variables?: Record<
54
+ string,
55
+ {
56
+ default: string
57
+ enum?: string[]
58
+ description?: string
59
+ }
60
+ >
61
+ }>
62
+
63
+ /**
64
+ * Security scheme definitions
65
+ */
66
+ securitySchemes?: Record<string, SecuritySchemeObject>
67
+
68
+ /**
69
+ * Global security requirements
70
+ */
71
+ security?: Array<Record<string, string[]>>
72
+
73
+ /**
74
+ * Tag definitions with descriptions
75
+ */
76
+ tags?: TagObject[]
77
+ }
78
+
79
+ /**
80
+ * Service responsible for generating the complete OpenAPI document.
81
+ *
82
+ * Orchestrates endpoint discovery, path generation, and document assembly.
83
+ */
84
+ @Injectable()
85
+ export class OpenApiGeneratorService {
86
+ private readonly logger = inject(Logger, {
87
+ context: OpenApiGeneratorService.name,
88
+ })
89
+
90
+ private readonly scanner = inject(EndpointScannerService)
91
+ private readonly pathBuilder = inject(PathBuilderService)
92
+
93
+ /**
94
+ * Generates an OpenAPI document from loaded modules.
95
+ *
96
+ * @param modules - Map of loaded modules
97
+ * @param options - OpenAPI generation options
98
+ * @returns Complete OpenAPI document
99
+ */
100
+ generate(
101
+ modules: Map<string, ModuleMetadata>,
102
+ options: OpenApiGeneratorOptions,
103
+ ): OpenAPIObject {
104
+ this.logger.debug('Generating OpenAPI document')
105
+
106
+ // Discover all endpoints
107
+ const endpoints = this.scanner.scan(modules)
108
+
109
+ // Generate paths
110
+ const paths = this.buildPaths(endpoints)
111
+
112
+ // Collect unique tags from endpoints
113
+ const discoveredTags = this.collectTags(endpoints)
114
+
115
+ // Merge discovered tags with configured tags
116
+ const tags = this.mergeTags(discoveredTags, options.tags)
117
+
118
+ // Build the OpenAPI document
119
+ const document: OpenAPIObject = {
120
+ openapi: '3.1.0',
121
+ info: options.info,
122
+ paths,
123
+ }
124
+
125
+ // Add optional fields
126
+ if (options.servers && options.servers.length > 0) {
127
+ document.servers = options.servers
128
+ }
129
+
130
+ if (options.externalDocs) {
131
+ document.externalDocs = options.externalDocs
132
+ }
133
+
134
+ if (tags.length > 0) {
135
+ document.tags = tags
136
+ }
137
+
138
+ if (options.security) {
139
+ document.security = options.security
140
+ }
141
+
142
+ if (options.securitySchemes) {
143
+ document.components = {
144
+ ...document.components,
145
+ securitySchemes: options.securitySchemes,
146
+ }
147
+ }
148
+
149
+ this.logger.debug(
150
+ `Generated OpenAPI document with ${Object.keys(paths).length} paths`,
151
+ )
152
+
153
+ return document
154
+ }
155
+
156
+ /**
157
+ * Builds paths object from discovered endpoints
158
+ */
159
+ private buildPaths(endpoints: DiscoveredEndpoint[]): PathsObject {
160
+ const paths: PathsObject = {}
161
+
162
+ for (const endpoint of endpoints) {
163
+ const { path, pathItem } = this.pathBuilder.build(endpoint)
164
+
165
+ // Merge with existing path if methods differ
166
+ if (paths[path]) {
167
+ paths[path] = {
168
+ ...paths[path],
169
+ ...pathItem,
170
+ }
171
+ } else {
172
+ paths[path] = pathItem
173
+ }
174
+ }
175
+
176
+ return paths
177
+ }
178
+
179
+ /**
180
+ * Collects unique tags from endpoints
181
+ */
182
+ private collectTags(endpoints: DiscoveredEndpoint[]): Set<string> {
183
+ const tags = new Set<string>()
184
+
185
+ for (const endpoint of endpoints) {
186
+ for (const tag of endpoint.openApiMetadata.tags) {
187
+ tags.add(tag)
188
+ }
189
+ }
190
+
191
+ return tags
192
+ }
193
+
194
+ /**
195
+ * Merges discovered tags with configured tags
196
+ */
197
+ private mergeTags(
198
+ discoveredTags: Set<string>,
199
+ configuredTags?: TagObject[],
200
+ ): TagObject[] {
201
+ const tagMap = new Map<string, TagObject>()
202
+
203
+ // Add configured tags first (they have descriptions)
204
+ if (configuredTags) {
205
+ for (const tag of configuredTags) {
206
+ tagMap.set(tag.name, tag)
207
+ }
208
+ }
209
+
210
+ // Add discovered tags that aren't already configured
211
+ for (const tagName of discoveredTags) {
212
+ if (!tagMap.has(tagName)) {
213
+ tagMap.set(tagName, { name: tagName })
214
+ }
215
+ }
216
+
217
+ return Array.from(tagMap.values())
218
+ }
219
+ }