@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.
- package/LICENSE +8 -0
- package/README.md +260 -0
- package/dist/src/__tests__/decorators.spec.d.mts +2 -0
- package/dist/src/__tests__/decorators.spec.d.mts.map +1 -0
- package/dist/src/__tests__/metadata.spec.d.mts +2 -0
- package/dist/src/__tests__/metadata.spec.d.mts.map +1 -0
- package/dist/src/__tests__/services.spec.d.mts +2 -0
- package/dist/src/__tests__/services.spec.d.mts.map +1 -0
- package/dist/src/decorators/api-deprecated.decorator.d.mts +33 -0
- package/dist/src/decorators/api-deprecated.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-exclude.decorator.d.mts +26 -0
- package/dist/src/decorators/api-exclude.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-operation.decorator.d.mts +58 -0
- package/dist/src/decorators/api-operation.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-security.decorator.d.mts +36 -0
- package/dist/src/decorators/api-security.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-stream.decorator.d.mts +50 -0
- package/dist/src/decorators/api-stream.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-summary.decorator.d.mts +24 -0
- package/dist/src/decorators/api-summary.decorator.d.mts.map +1 -0
- package/dist/src/decorators/api-tag.decorator.d.mts +42 -0
- package/dist/src/decorators/api-tag.decorator.d.mts.map +1 -0
- package/dist/src/decorators/index.d.mts +8 -0
- package/dist/src/decorators/index.d.mts.map +1 -0
- package/dist/src/index.d.mts +5 -0
- package/dist/src/index.d.mts.map +1 -0
- package/dist/src/metadata/index.d.mts +2 -0
- package/dist/src/metadata/index.d.mts.map +1 -0
- package/dist/src/metadata/openapi.metadata.d.mts +30 -0
- package/dist/src/metadata/openapi.metadata.d.mts.map +1 -0
- package/dist/src/services/endpoint-scanner.service.d.mts +42 -0
- package/dist/src/services/endpoint-scanner.service.d.mts.map +1 -0
- package/dist/src/services/index.d.mts +6 -0
- package/dist/src/services/index.d.mts.map +1 -0
- package/dist/src/services/metadata-extractor.service.d.mts +19 -0
- package/dist/src/services/metadata-extractor.service.d.mts.map +1 -0
- package/dist/src/services/openapi-generator.service.d.mts +91 -0
- package/dist/src/services/openapi-generator.service.d.mts.map +1 -0
- package/dist/src/services/path-builder.service.d.mts +73 -0
- package/dist/src/services/path-builder.service.d.mts.map +1 -0
- package/dist/src/services/schema-converter.service.d.mts +57 -0
- package/dist/src/services/schema-converter.service.d.mts.map +1 -0
- package/dist/src/tokens/index.d.mts +18 -0
- package/dist/src/tokens/index.d.mts.map +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/tsconfig.spec.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsdown.config.d.mts +3 -0
- package/dist/tsdown.config.d.mts.map +1 -0
- package/dist/vitest.config.d.mts +3 -0
- package/dist/vitest.config.d.mts.map +1 -0
- package/lib/index.cjs +2120 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +594 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +594 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +2077 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +44 -0
- package/project.json +66 -0
- package/src/__tests__/decorators.spec.mts +185 -0
- package/src/__tests__/metadata.spec.mts +207 -0
- package/src/__tests__/services.spec.mts +216 -0
- package/src/decorators/api-deprecated.decorator.mts +45 -0
- package/src/decorators/api-exclude.decorator.mts +29 -0
- package/src/decorators/api-operation.decorator.mts +59 -0
- package/src/decorators/api-security.decorator.mts +44 -0
- package/src/decorators/api-stream.decorator.mts +55 -0
- package/src/decorators/api-summary.decorator.mts +33 -0
- package/src/decorators/api-tag.decorator.mts +51 -0
- package/src/decorators/index.mts +7 -0
- package/src/index.mts +42 -0
- package/src/metadata/index.mts +2 -0
- package/src/metadata/openapi.metadata.mts +30 -0
- package/src/services/endpoint-scanner.service.mts +118 -0
- package/src/services/index.mts +21 -0
- package/src/services/metadata-extractor.service.mts +91 -0
- package/src/services/openapi-generator.service.mts +219 -0
- package/src/services/path-builder.service.mts +344 -0
- package/src/services/schema-converter.service.mts +96 -0
- package/src/tokens/index.mts +24 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +12 -0
- package/tsdown.config.mts +35 -0
- package/vitest.config.mts +11 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { TestContainer } from '@navios/di'
|
|
5
|
+
|
|
6
|
+
import { SchemaConverterService } from '../services/schema-converter.service.mjs'
|
|
7
|
+
import { PathBuilderService } from '../services/path-builder.service.mjs'
|
|
8
|
+
|
|
9
|
+
describe('OpenAPI Services', () => {
|
|
10
|
+
let container: TestContainer
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
container = new TestContainer()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await container.dispose()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('SchemaConverterService', () => {
|
|
21
|
+
it('should convert string schema', async () => {
|
|
22
|
+
const service = await container.get(SchemaConverterService)
|
|
23
|
+
const schema = z.string()
|
|
24
|
+
const result = service.convert(schema)
|
|
25
|
+
|
|
26
|
+
expect(result.schema).toEqual({ type: 'string' })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should convert number schema', async () => {
|
|
30
|
+
const service = await container.get(SchemaConverterService)
|
|
31
|
+
const schema = z.number()
|
|
32
|
+
const result = service.convert(schema)
|
|
33
|
+
|
|
34
|
+
expect(result.schema).toEqual({ type: 'number' })
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should convert boolean schema', async () => {
|
|
38
|
+
const service = await container.get(SchemaConverterService)
|
|
39
|
+
const schema = z.boolean()
|
|
40
|
+
const result = service.convert(schema)
|
|
41
|
+
|
|
42
|
+
expect(result.schema).toEqual({ type: 'boolean' })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should convert object schema', async () => {
|
|
46
|
+
const service = await container.get(SchemaConverterService)
|
|
47
|
+
const schema = z.object({
|
|
48
|
+
id: z.string(),
|
|
49
|
+
name: z.string(),
|
|
50
|
+
age: z.number().optional(),
|
|
51
|
+
})
|
|
52
|
+
const result = service.convert(schema)
|
|
53
|
+
|
|
54
|
+
expect(result.schema).toMatchObject({
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
id: { type: 'string' },
|
|
58
|
+
name: { type: 'string' },
|
|
59
|
+
age: { type: 'number' },
|
|
60
|
+
},
|
|
61
|
+
required: ['id', 'name'],
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should convert array schema', async () => {
|
|
66
|
+
const service = await container.get(SchemaConverterService)
|
|
67
|
+
const schema = z.array(z.string())
|
|
68
|
+
const result = service.convert(schema)
|
|
69
|
+
|
|
70
|
+
expect(result.schema).toMatchObject({
|
|
71
|
+
type: 'array',
|
|
72
|
+
items: { type: 'string' },
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should convert enum schema', async () => {
|
|
77
|
+
const service = await container.get(SchemaConverterService)
|
|
78
|
+
const schema = z.enum(['admin', 'user', 'guest'])
|
|
79
|
+
const result = service.convert(schema)
|
|
80
|
+
|
|
81
|
+
expect(result.schema).toMatchObject({
|
|
82
|
+
type: 'string',
|
|
83
|
+
enum: ['admin', 'user', 'guest'],
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should convert union schema', async () => {
|
|
88
|
+
const service = await container.get(SchemaConverterService)
|
|
89
|
+
const schema = z.union([z.string(), z.number()])
|
|
90
|
+
const result = service.convert(schema)
|
|
91
|
+
|
|
92
|
+
expect(result.schema).toMatchObject({
|
|
93
|
+
anyOf: [{ type: 'string' }, { type: 'number' }],
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should handle nullable schema', async () => {
|
|
98
|
+
const service = await container.get(SchemaConverterService)
|
|
99
|
+
const schema = z.string().nullable()
|
|
100
|
+
const result = service.convert(schema)
|
|
101
|
+
|
|
102
|
+
// zod-openapi uses anyOf for nullable types in OpenAPI 3.1
|
|
103
|
+
expect(result.schema).toMatchObject({
|
|
104
|
+
anyOf: [{ type: 'string' }, { type: 'null' }],
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should handle email format', async () => {
|
|
109
|
+
const service = await container.get(SchemaConverterService)
|
|
110
|
+
const schema = z.string().email()
|
|
111
|
+
const result = service.convert(schema)
|
|
112
|
+
|
|
113
|
+
expect(result.schema).toMatchObject({
|
|
114
|
+
type: 'string',
|
|
115
|
+
format: 'email',
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should handle uuid format', async () => {
|
|
120
|
+
const service = await container.get(SchemaConverterService)
|
|
121
|
+
const schema = z.string().uuid()
|
|
122
|
+
const result = service.convert(schema)
|
|
123
|
+
|
|
124
|
+
expect(result.schema).toMatchObject({
|
|
125
|
+
type: 'string',
|
|
126
|
+
format: 'uuid',
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should handle date schema', async () => {
|
|
131
|
+
const service = await container.get(SchemaConverterService)
|
|
132
|
+
const schema = z.date()
|
|
133
|
+
const result = service.convert(schema)
|
|
134
|
+
|
|
135
|
+
// zod-openapi converts z.date() to just string type
|
|
136
|
+
expect(result.schema).toMatchObject({
|
|
137
|
+
type: 'string',
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should handle nested objects', async () => {
|
|
142
|
+
const service = await container.get(SchemaConverterService)
|
|
143
|
+
const schema = z.object({
|
|
144
|
+
user: z.object({
|
|
145
|
+
id: z.string(),
|
|
146
|
+
profile: z.object({
|
|
147
|
+
bio: z.string().optional(),
|
|
148
|
+
}),
|
|
149
|
+
}),
|
|
150
|
+
})
|
|
151
|
+
const result = service.convert(schema)
|
|
152
|
+
|
|
153
|
+
expect(result.schema).toMatchObject({
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
user: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
properties: {
|
|
159
|
+
id: { type: 'string' },
|
|
160
|
+
profile: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
bio: { type: 'string' },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe('PathBuilderService', () => {
|
|
174
|
+
it('should convert $param to {param} format', async () => {
|
|
175
|
+
const service = await container.get(PathBuilderService)
|
|
176
|
+
expect(service.convertUrlParams('/users/$userId')).toBe('/users/{userId}')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('should convert multiple params', async () => {
|
|
180
|
+
const service = await container.get(PathBuilderService)
|
|
181
|
+
expect(service.convertUrlParams('/users/$userId/posts/$postId')).toBe(
|
|
182
|
+
'/users/{userId}/posts/{postId}',
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should handle URLs without params', async () => {
|
|
187
|
+
const service = await container.get(PathBuilderService)
|
|
188
|
+
expect(service.convertUrlParams('/users')).toBe('/users')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should handle params at the end', async () => {
|
|
192
|
+
const service = await container.get(PathBuilderService)
|
|
193
|
+
expect(service.convertUrlParams('/files/$fileId/download')).toBe(
|
|
194
|
+
'/files/{fileId}/download',
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should extract single param', async () => {
|
|
199
|
+
const service = await container.get(PathBuilderService)
|
|
200
|
+
expect(service.extractUrlParamNames('/users/$userId')).toEqual(['userId'])
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should extract multiple params', async () => {
|
|
204
|
+
const service = await container.get(PathBuilderService)
|
|
205
|
+
expect(service.extractUrlParamNames('/users/$userId/posts/$postId')).toEqual([
|
|
206
|
+
'userId',
|
|
207
|
+
'postId',
|
|
208
|
+
])
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should return empty array for no params', async () => {
|
|
212
|
+
const service = await container.get(PathBuilderService)
|
|
213
|
+
expect(service.extractUrlParamNames('/users')).toEqual([])
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { ApiDeprecatedToken } from '../tokens/index.mjs'
|
|
5
|
+
|
|
6
|
+
const ApiDeprecatedSchema = z.object({
|
|
7
|
+
message: z.string().optional(),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
/** Options for the @ApiDeprecated decorator, inferred from the schema */
|
|
11
|
+
export type ApiDeprecatedOptions = z.infer<typeof ApiDeprecatedSchema>
|
|
12
|
+
|
|
13
|
+
const BaseApiDeprecated = AttributeFactory.createAttribute(
|
|
14
|
+
ApiDeprecatedToken,
|
|
15
|
+
ApiDeprecatedSchema,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Marks an endpoint as deprecated.
|
|
20
|
+
*
|
|
21
|
+
* Deprecated endpoints are shown with a visual indicator in documentation
|
|
22
|
+
* and may include a migration message.
|
|
23
|
+
*
|
|
24
|
+
* @param message - Optional deprecation message
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* @Controller()
|
|
29
|
+
* export class UserController {
|
|
30
|
+
* // Simple deprecation
|
|
31
|
+
* @Endpoint(legacyGetUser)
|
|
32
|
+
* @ApiDeprecated()
|
|
33
|
+
* async getLegacyUser() {}
|
|
34
|
+
*
|
|
35
|
+
* // With migration message
|
|
36
|
+
* @Endpoint(oldCreateUser)
|
|
37
|
+
* @ApiDeprecated('Use POST /v2/users instead')
|
|
38
|
+
* async oldCreateUser() {}
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function ApiDeprecated(message?: string) {
|
|
43
|
+
return BaseApiDeprecated({ message })
|
|
44
|
+
}
|
|
45
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
|
|
3
|
+
import { ApiExcludeToken } from '../tokens/index.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Excludes an endpoint from OpenAPI documentation.
|
|
7
|
+
*
|
|
8
|
+
* Use this decorator for internal endpoints that should not be visible
|
|
9
|
+
* in the public API documentation.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* @Controller()
|
|
14
|
+
* export class HealthController {
|
|
15
|
+
* @Endpoint(healthCheck)
|
|
16
|
+
* @ApiExclude()
|
|
17
|
+
* async healthCheck() {
|
|
18
|
+
* return { status: 'ok' }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @Endpoint(internalMetrics)
|
|
22
|
+
* @ApiExclude()
|
|
23
|
+
* async internalMetrics() {
|
|
24
|
+
* return { ... }
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const ApiExclude = AttributeFactory.createAttribute(ApiExcludeToken)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { ApiOperationToken } from '../tokens/index.mjs'
|
|
5
|
+
|
|
6
|
+
const ApiOperationSchema = z.object({
|
|
7
|
+
summary: z.string().optional(),
|
|
8
|
+
description: z.string().optional(),
|
|
9
|
+
operationId: z.string().optional(),
|
|
10
|
+
deprecated: z.boolean().optional(),
|
|
11
|
+
externalDocs: z
|
|
12
|
+
.object({
|
|
13
|
+
url: z.string(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
})
|
|
16
|
+
.optional(),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
/** Options for the @ApiOperation decorator, inferred from the schema */
|
|
20
|
+
export type ApiOperationOptions = z.infer<typeof ApiOperationSchema>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Provides detailed operation metadata for an endpoint.
|
|
24
|
+
*
|
|
25
|
+
* Use this decorator when you need to specify multiple operation properties.
|
|
26
|
+
* For simple cases, consider using @ApiSummary instead.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Operation configuration options
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* @Controller()
|
|
33
|
+
* export class UserController {
|
|
34
|
+
* @Endpoint(getUser)
|
|
35
|
+
* @ApiOperation({
|
|
36
|
+
* summary: 'Get user by ID',
|
|
37
|
+
* description: 'Retrieves a user by their unique identifier. Returns 404 if not found.',
|
|
38
|
+
* operationId: 'getUserById',
|
|
39
|
+
* })
|
|
40
|
+
* async getUser(params: EndpointParams<typeof getUser>) {}
|
|
41
|
+
*
|
|
42
|
+
* @Endpoint(legacyGetUser)
|
|
43
|
+
* @ApiOperation({
|
|
44
|
+
* summary: 'Get user (legacy)',
|
|
45
|
+
* deprecated: true,
|
|
46
|
+
* externalDocs: {
|
|
47
|
+
* url: 'https://docs.example.com/migration',
|
|
48
|
+
* description: 'Migration guide'
|
|
49
|
+
* }
|
|
50
|
+
* })
|
|
51
|
+
* async getLegacyUser() {}
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const ApiOperation = AttributeFactory.createAttribute(
|
|
56
|
+
ApiOperationToken,
|
|
57
|
+
ApiOperationSchema,
|
|
58
|
+
)
|
|
59
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { ApiSecurityToken } from '../tokens/index.mjs'
|
|
5
|
+
|
|
6
|
+
const ApiSecuritySchema = z.record(z.string(), z.array(z.string()))
|
|
7
|
+
|
|
8
|
+
/** Security requirement for an endpoint, inferred from the schema */
|
|
9
|
+
export type ApiSecurityRequirement = z.infer<typeof ApiSecuritySchema>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Specifies security requirements for an endpoint.
|
|
13
|
+
*
|
|
14
|
+
* The security requirement object maps security scheme names to their scopes.
|
|
15
|
+
* For schemes that don't use scopes (like API keys), use an empty array.
|
|
16
|
+
*
|
|
17
|
+
* @param requirements - Security requirements object
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* @Controller()
|
|
22
|
+
* export class UserController {
|
|
23
|
+
* // Require bearer token authentication
|
|
24
|
+
* @Endpoint(getUser)
|
|
25
|
+
* @ApiSecurity({ bearerAuth: [] })
|
|
26
|
+
* async getUser() {}
|
|
27
|
+
*
|
|
28
|
+
* // Require multiple authentication methods
|
|
29
|
+
* @Endpoint(adminEndpoint)
|
|
30
|
+
* @ApiSecurity({ bearerAuth: [], apiKey: [] })
|
|
31
|
+
* async adminAction() {}
|
|
32
|
+
*
|
|
33
|
+
* // OAuth2 with specific scopes
|
|
34
|
+
* @Endpoint(writeUser)
|
|
35
|
+
* @ApiSecurity({ oauth2: ['users:write', 'users:read'] })
|
|
36
|
+
* async writeUser() {}
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export const ApiSecurity = AttributeFactory.createAttribute(
|
|
41
|
+
ApiSecurityToken,
|
|
42
|
+
ApiSecuritySchema,
|
|
43
|
+
)
|
|
44
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { ApiStreamToken } from '../tokens/index.mjs'
|
|
5
|
+
|
|
6
|
+
const ApiStreamSchema = z.object({
|
|
7
|
+
contentType: z.string(),
|
|
8
|
+
description: z.string().optional(),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
/** Options for the @ApiStream decorator, inferred from the schema */
|
|
12
|
+
export type ApiStreamOptions = z.infer<typeof ApiStreamSchema>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Specifies content type and description for stream endpoints.
|
|
16
|
+
*
|
|
17
|
+
* Stream endpoints don't have a responseSchema, so this decorator provides
|
|
18
|
+
* the necessary metadata for OpenAPI documentation.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Stream response options
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* @Controller()
|
|
25
|
+
* export class FileController {
|
|
26
|
+
* // Binary file download
|
|
27
|
+
* @Stream(downloadFile)
|
|
28
|
+
* @ApiStream({
|
|
29
|
+
* contentType: 'application/octet-stream',
|
|
30
|
+
* description: 'Download file as binary stream'
|
|
31
|
+
* })
|
|
32
|
+
* async download(params: StreamParams<typeof downloadFile>, reply: Reply) {
|
|
33
|
+
* // Stream implementation
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* @Controller()
|
|
38
|
+
* export class EventController {
|
|
39
|
+
* // Server-Sent Events
|
|
40
|
+
* @Stream(streamEvents)
|
|
41
|
+
* @ApiStream({
|
|
42
|
+
* contentType: 'text/event-stream',
|
|
43
|
+
* description: 'Real-time event stream'
|
|
44
|
+
* })
|
|
45
|
+
* async stream(params: StreamParams<typeof streamEvents>, reply: Reply) {
|
|
46
|
+
* // SSE implementation
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export const ApiStream = AttributeFactory.createAttribute(
|
|
52
|
+
ApiStreamToken,
|
|
53
|
+
ApiStreamSchema,
|
|
54
|
+
)
|
|
55
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { ApiSummaryToken } from '../tokens/index.mjs'
|
|
5
|
+
|
|
6
|
+
const ApiSummarySchema = z.string()
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shorthand decorator for adding just a summary to an endpoint.
|
|
10
|
+
*
|
|
11
|
+
* This is equivalent to `@ApiOperation({ summary: '...' })` but more concise.
|
|
12
|
+
*
|
|
13
|
+
* @param summary - Short summary text for the endpoint
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Controller()
|
|
18
|
+
* export class UserController {
|
|
19
|
+
* @Endpoint(getUser)
|
|
20
|
+
* @ApiSummary('Get user by ID')
|
|
21
|
+
* async getUser() {}
|
|
22
|
+
*
|
|
23
|
+
* @Endpoint(createUser)
|
|
24
|
+
* @ApiSummary('Create a new user')
|
|
25
|
+
* async createUser() {}
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const ApiSummary = AttributeFactory.createAttribute(
|
|
30
|
+
ApiSummaryToken,
|
|
31
|
+
ApiSummarySchema,
|
|
32
|
+
)
|
|
33
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AttributeFactory } from '@navios/core'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import { ApiTagToken } from '../tokens/index.mjs'
|
|
5
|
+
|
|
6
|
+
const ApiTagSchema = z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
description: z.string().optional(),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
/** Options for the @ApiTag decorator, inferred from the schema */
|
|
12
|
+
export type ApiTagOptions = z.infer<typeof ApiTagSchema>
|
|
13
|
+
|
|
14
|
+
const BaseApiTag = AttributeFactory.createAttribute(ApiTagToken, ApiTagSchema)
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Groups endpoints under a specific tag/folder in the documentation.
|
|
18
|
+
*
|
|
19
|
+
* Can be applied to controllers (affects all endpoints) or individual methods.
|
|
20
|
+
* When applied to both, the method-level tag takes precedence.
|
|
21
|
+
*
|
|
22
|
+
* @param name - The tag name
|
|
23
|
+
* @param description - Optional tag description
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Apply to entire controller
|
|
28
|
+
* @Controller()
|
|
29
|
+
* @ApiTag('Users', 'User management operations')
|
|
30
|
+
* export class UserController {
|
|
31
|
+
* @Endpoint(getUser)
|
|
32
|
+
* async getUser() {}
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* // Apply to individual endpoint
|
|
36
|
+
* @Controller()
|
|
37
|
+
* export class MixedController {
|
|
38
|
+
* @Endpoint(getUser)
|
|
39
|
+
* @ApiTag('Users')
|
|
40
|
+
* async getUser() {}
|
|
41
|
+
*
|
|
42
|
+
* @Endpoint(getOrder)
|
|
43
|
+
* @ApiTag('Orders')
|
|
44
|
+
* async getOrder() {}
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function ApiTag(name: string, description?: string) {
|
|
49
|
+
return BaseApiTag({ name, description })
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ApiTag, type ApiTagOptions } from './api-tag.decorator.mjs'
|
|
2
|
+
export { ApiOperation, type ApiOperationOptions } from './api-operation.decorator.mjs'
|
|
3
|
+
export { ApiSummary } from './api-summary.decorator.mjs'
|
|
4
|
+
export { ApiDeprecated, type ApiDeprecatedOptions } from './api-deprecated.decorator.mjs'
|
|
5
|
+
export { ApiSecurity, type ApiSecurityRequirement } from './api-security.decorator.mjs'
|
|
6
|
+
export { ApiExclude } from './api-exclude.decorator.mjs'
|
|
7
|
+
export { ApiStream, type ApiStreamOptions } from './api-stream.decorator.mjs'
|
package/src/index.mts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Decorators and their types
|
|
2
|
+
export {
|
|
3
|
+
ApiTag,
|
|
4
|
+
ApiOperation,
|
|
5
|
+
ApiSummary,
|
|
6
|
+
ApiDeprecated,
|
|
7
|
+
ApiSecurity,
|
|
8
|
+
ApiExclude,
|
|
9
|
+
ApiStream,
|
|
10
|
+
type ApiOperationOptions,
|
|
11
|
+
type ApiTagOptions,
|
|
12
|
+
type ApiStreamOptions,
|
|
13
|
+
type ApiDeprecatedOptions,
|
|
14
|
+
type ApiSecurityRequirement,
|
|
15
|
+
} from './decorators/index.mjs'
|
|
16
|
+
|
|
17
|
+
// Metadata types
|
|
18
|
+
export type { OpenApiEndpointMetadata } from './metadata/index.mjs'
|
|
19
|
+
|
|
20
|
+
// Services (Injectable classes - recommended for DI integration)
|
|
21
|
+
export {
|
|
22
|
+
OpenApiGeneratorService,
|
|
23
|
+
type OpenApiGeneratorOptions,
|
|
24
|
+
EndpointScannerService,
|
|
25
|
+
type DiscoveredEndpoint,
|
|
26
|
+
MetadataExtractorService,
|
|
27
|
+
SchemaConverterService,
|
|
28
|
+
type SchemaConversionResult,
|
|
29
|
+
PathBuilderService,
|
|
30
|
+
type PathItemResult,
|
|
31
|
+
} from './services/index.mjs'
|
|
32
|
+
|
|
33
|
+
// Tokens (for advanced usage)
|
|
34
|
+
export {
|
|
35
|
+
ApiTagToken,
|
|
36
|
+
ApiOperationToken,
|
|
37
|
+
ApiSummaryToken,
|
|
38
|
+
ApiDeprecatedToken,
|
|
39
|
+
ApiSecurityToken,
|
|
40
|
+
ApiExcludeToken,
|
|
41
|
+
ApiStreamToken,
|
|
42
|
+
} from './tokens/index.mjs'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ApiSecurityRequirement, ApiStreamOptions } from '../decorators/index.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracted OpenAPI metadata for an endpoint
|
|
5
|
+
*/
|
|
6
|
+
export interface OpenApiEndpointMetadata {
|
|
7
|
+
/** Tags for grouping endpoints */
|
|
8
|
+
tags: string[]
|
|
9
|
+
/** Short summary */
|
|
10
|
+
summary?: string
|
|
11
|
+
/** Detailed description */
|
|
12
|
+
description?: string
|
|
13
|
+
/** Unique operation identifier */
|
|
14
|
+
operationId?: string
|
|
15
|
+
/** Whether the endpoint is deprecated */
|
|
16
|
+
deprecated: boolean
|
|
17
|
+
/** Deprecation message */
|
|
18
|
+
deprecationMessage?: string
|
|
19
|
+
/** Security requirements */
|
|
20
|
+
security?: ApiSecurityRequirement[]
|
|
21
|
+
/** External documentation link */
|
|
22
|
+
externalDocs?: {
|
|
23
|
+
url: string
|
|
24
|
+
description?: string
|
|
25
|
+
}
|
|
26
|
+
/** Stream content type (for stream endpoints) */
|
|
27
|
+
stream?: ApiStreamOptions
|
|
28
|
+
/** Whether to exclude from documentation */
|
|
29
|
+
excluded: boolean
|
|
30
|
+
}
|