@hazeljs/swagger 0.2.0-alpha.1

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.md ADDED
@@ -0,0 +1,584 @@
1
+ # @hazeljs/swagger
2
+
3
+ **API docs that write themselves.**
4
+
5
+ Add `@ApiOperation` and `@ApiResponse` — get Swagger UI. No manual OpenAPI JSON. Decorators on your controllers, interactive docs at `/api-docs`. Your frontend team will thank you.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@hazeljs/swagger.svg)](https://www.npmjs.com/package/@hazeljs/swagger)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/swagger)](https://www.npmjs.com/package/@hazeljs/swagger)
9
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
10
+
11
+ ## Features
12
+
13
+ - 📚 **Auto-Generated Docs** - Automatic OpenAPI spec generation
14
+ - 🎨 **Swagger UI** - Interactive API explorer
15
+ - 🏷️ **Decorator-Based** - Document APIs with decorators
16
+ - 📝 **Type Safety** - TypeScript integration
17
+ - 🔐 **Authentication** - Document auth requirements
18
+ - 📊 **Request/Response Examples** - Add example payloads
19
+ - 🎯 **Tags & Groups** - Organize endpoints
20
+ - 🔄 **Multiple Formats** - JSON, YAML export
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @hazeljs/swagger
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Configure Swagger Module
31
+
32
+ ```typescript
33
+ import { HazelModule } from '@hazeljs/core';
34
+ import { SwaggerModule } from '@hazeljs/swagger';
35
+
36
+ @HazelModule({
37
+ imports: [
38
+ SwaggerModule.forRoot({
39
+ title: 'My API',
40
+ description: 'API documentation',
41
+ version: '1.0.0',
42
+ path: '/api-docs',
43
+ }),
44
+ ],
45
+ })
46
+ export class AppModule {}
47
+ ```
48
+
49
+ ### 2. Document Controllers
50
+
51
+ ```typescript
52
+ import { Controller, Get, Post, Body, Param } from '@hazeljs/core';
53
+ import { ApiOperation, ApiResponse, ApiTags } from '@hazeljs/swagger';
54
+
55
+ @Controller('/users')
56
+ @ApiTags('Users')
57
+ export class UserController {
58
+ @Get()
59
+ @ApiOperation({ summary: 'Get all users' })
60
+ @ApiResponse({ status: 200, description: 'List of users' })
61
+ findAll() {
62
+ return { users: [] };
63
+ }
64
+
65
+ @Get('/:id')
66
+ @ApiOperation({ summary: 'Get user by ID' })
67
+ @ApiResponse({ status: 200, description: 'User found' })
68
+ @ApiResponse({ status: 404, description: 'User not found' })
69
+ findOne(@Param('id') id: string) {
70
+ return { id, name: 'John Doe' };
71
+ }
72
+
73
+ @Post()
74
+ @ApiOperation({ summary: 'Create a new user' })
75
+ @ApiResponse({ status: 201, description: 'User created' })
76
+ @ApiResponse({ status: 400, description: 'Invalid input' })
77
+ create(@Body() createUserDto: CreateUserDto) {
78
+ return createUserDto;
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### 3. Access Documentation
84
+
85
+ Navigate to `http://localhost:3000/api-docs` to view the interactive Swagger UI.
86
+
87
+ ## Decorators
88
+
89
+ ### @ApiTags()
90
+
91
+ Group endpoints by tags:
92
+
93
+ ```typescript
94
+ @Controller('/products')
95
+ @ApiTags('Products', 'Catalog')
96
+ export class ProductController {
97
+ // All endpoints will be tagged with 'Products' and 'Catalog'
98
+ }
99
+ ```
100
+
101
+ ### @ApiOperation()
102
+
103
+ Document endpoint details:
104
+
105
+ ```typescript
106
+ @Get('/search')
107
+ @ApiOperation({
108
+ summary: 'Search products',
109
+ description: 'Search for products by name, category, or tags',
110
+ operationId: 'searchProducts',
111
+ })
112
+ searchProducts() {
113
+ return [];
114
+ }
115
+ ```
116
+
117
+ ### @ApiResponse()
118
+
119
+ Document response types:
120
+
121
+ ```typescript
122
+ @Get('/:id')
123
+ @ApiResponse({
124
+ status: 200,
125
+ description: 'Product found',
126
+ type: ProductDto,
127
+ })
128
+ @ApiResponse({
129
+ status: 404,
130
+ description: 'Product not found',
131
+ schema: {
132
+ type: 'object',
133
+ properties: {
134
+ statusCode: { type: 'number' },
135
+ message: { type: 'string' },
136
+ },
137
+ },
138
+ })
139
+ findOne(@Param('id') id: string) {
140
+ return this.productService.findOne(id);
141
+ }
142
+ ```
143
+
144
+ ### @ApiProperty()
145
+
146
+ Document DTO properties:
147
+
148
+ ```typescript
149
+ import { ApiProperty } from '@hazeljs/swagger';
150
+
151
+ export class CreateUserDto {
152
+ @ApiProperty({
153
+ description: 'User email address',
154
+ example: 'user@example.com',
155
+ })
156
+ email: string;
157
+
158
+ @ApiProperty({
159
+ description: 'User password',
160
+ minLength: 8,
161
+ example: 'SecurePass123!',
162
+ })
163
+ password: string;
164
+
165
+ @ApiProperty({
166
+ description: 'User full name',
167
+ example: 'John Doe',
168
+ })
169
+ name: string;
170
+
171
+ @ApiProperty({
172
+ description: 'User age',
173
+ minimum: 18,
174
+ maximum: 120,
175
+ example: 25,
176
+ required: false,
177
+ })
178
+ age?: number;
179
+ }
180
+ ```
181
+
182
+ ### @ApiParam()
183
+
184
+ Document path parameters:
185
+
186
+ ```typescript
187
+ @Get('/:id')
188
+ @ApiParam({
189
+ name: 'id',
190
+ description: 'User ID',
191
+ type: 'string',
192
+ example: '123',
193
+ })
194
+ findOne(@Param('id') id: string) {
195
+ return this.userService.findOne(id);
196
+ }
197
+ ```
198
+
199
+ ### @ApiQuery()
200
+
201
+ Document query parameters:
202
+
203
+ ```typescript
204
+ @Get()
205
+ @ApiQuery({
206
+ name: 'page',
207
+ required: false,
208
+ type: Number,
209
+ description: 'Page number',
210
+ example: 1,
211
+ })
212
+ @ApiQuery({
213
+ name: 'limit',
214
+ required: false,
215
+ type: Number,
216
+ description: 'Items per page',
217
+ example: 10,
218
+ })
219
+ findAll(
220
+ @Query('page') page: number = 1,
221
+ @Query('limit') limit: number = 10
222
+ ) {
223
+ return this.userService.findAll(page, limit);
224
+ }
225
+ ```
226
+
227
+ ### @ApiBody()
228
+
229
+ Document request body:
230
+
231
+ ```typescript
232
+ @Post()
233
+ @ApiBody({
234
+ description: 'User data',
235
+ type: CreateUserDto,
236
+ examples: {
237
+ user1: {
238
+ summary: 'Example user',
239
+ value: {
240
+ email: 'john@example.com',
241
+ password: 'SecurePass123!',
242
+ name: 'John Doe',
243
+ },
244
+ },
245
+ },
246
+ })
247
+ create(@Body() createUserDto: CreateUserDto) {
248
+ return this.userService.create(createUserDto);
249
+ }
250
+ ```
251
+
252
+ ### @ApiHeader()
253
+
254
+ Document required headers:
255
+
256
+ ```typescript
257
+ @Get('/protected')
258
+ @ApiHeader({
259
+ name: 'Authorization',
260
+ description: 'Bearer token',
261
+ required: true,
262
+ example: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
263
+ })
264
+ getProtectedData() {
265
+ return { data: 'protected' };
266
+ }
267
+ ```
268
+
269
+ ### @ApiBearerAuth()
270
+
271
+ Document bearer authentication:
272
+
273
+ ```typescript
274
+ @Controller('/admin')
275
+ @ApiBearerAuth()
276
+ export class AdminController {
277
+ @Get('/dashboard')
278
+ getDashboard() {
279
+ return { data: 'admin dashboard' };
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### @ApiSecurity()
285
+
286
+ Document custom security:
287
+
288
+ ```typescript
289
+ @Controller('/api')
290
+ @ApiSecurity('api_key')
291
+ export class ApiController {
292
+ @Get('/data')
293
+ getData() {
294
+ return { data: [] };
295
+ }
296
+ }
297
+ ```
298
+
299
+ ## Configuration
300
+
301
+ ### Full Configuration
302
+
303
+ ```typescript
304
+ SwaggerModule.forRoot({
305
+ // Basic info
306
+ title: 'My API',
307
+ description: 'Comprehensive API documentation',
308
+ version: '1.0.0',
309
+
310
+ // Server info
311
+ servers: [
312
+ {
313
+ url: 'http://localhost:3000',
314
+ description: 'Development server',
315
+ },
316
+ {
317
+ url: 'https://api.example.com',
318
+ description: 'Production server',
319
+ },
320
+ ],
321
+
322
+ // Contact info
323
+ contact: {
324
+ name: 'API Support',
325
+ email: 'support@example.com',
326
+ url: 'https://example.com/support',
327
+ },
328
+
329
+ // License
330
+ license: {
331
+ name: 'Apache-2.0',
332
+ url: 'https://www.apache.org/licenses/LICENSE-2.0',
333
+ },
334
+
335
+ // Terms of service
336
+ termsOfService: 'https://example.com/terms',
337
+
338
+ // External docs
339
+ externalDocs: {
340
+ description: 'Find more info here',
341
+ url: 'https://docs.example.com',
342
+ },
343
+
344
+ // Security schemes
345
+ security: [
346
+ {
347
+ name: 'bearer',
348
+ type: 'http',
349
+ scheme: 'bearer',
350
+ bearerFormat: 'JWT',
351
+ },
352
+ {
353
+ name: 'api_key',
354
+ type: 'apiKey',
355
+ in: 'header',
356
+ name: 'X-API-Key',
357
+ },
358
+ ],
359
+
360
+ // Swagger UI options
361
+ swaggerOptions: {
362
+ persistAuthorization: true,
363
+ displayRequestDuration: true,
364
+ filter: true,
365
+ showExtensions: true,
366
+ },
367
+
368
+ // Path to serve docs
369
+ path: '/api-docs',
370
+
371
+ // Custom CSS
372
+ customCss: '.swagger-ui .topbar { display: none }',
373
+
374
+ // Custom site title
375
+ customSiteTitle: 'My API Documentation',
376
+ })
377
+ ```
378
+
379
+ ## Authentication
380
+
381
+ ### JWT Bearer
382
+
383
+ ```typescript
384
+ // Configure security scheme
385
+ SwaggerModule.forRoot({
386
+ security: [
387
+ {
388
+ name: 'bearer',
389
+ type: 'http',
390
+ scheme: 'bearer',
391
+ bearerFormat: 'JWT',
392
+ },
393
+ ],
394
+ });
395
+
396
+ // Use in controller
397
+ @Controller('/protected')
398
+ @ApiBearerAuth()
399
+ export class ProtectedController {
400
+ @Get()
401
+ getData() {
402
+ return { data: 'protected' };
403
+ }
404
+ }
405
+ ```
406
+
407
+ ### API Key
408
+
409
+ ```typescript
410
+ // Configure security scheme
411
+ SwaggerModule.forRoot({
412
+ security: [
413
+ {
414
+ name: 'api_key',
415
+ type: 'apiKey',
416
+ in: 'header',
417
+ name: 'X-API-Key',
418
+ },
419
+ ],
420
+ });
421
+
422
+ // Use in controller
423
+ @Controller('/api')
424
+ @ApiSecurity('api_key')
425
+ export class ApiController {
426
+ @Get()
427
+ getData() {
428
+ return { data: [] };
429
+ }
430
+ }
431
+ ```
432
+
433
+ ### OAuth2
434
+
435
+ ```typescript
436
+ SwaggerModule.forRoot({
437
+ security: [
438
+ {
439
+ name: 'oauth2',
440
+ type: 'oauth2',
441
+ flows: {
442
+ authorizationCode: {
443
+ authorizationUrl: 'https://example.com/oauth/authorize',
444
+ tokenUrl: 'https://example.com/oauth/token',
445
+ scopes: {
446
+ 'read:users': 'Read user information',
447
+ 'write:users': 'Modify user information',
448
+ },
449
+ },
450
+ },
451
+ },
452
+ ],
453
+ });
454
+ ```
455
+
456
+ ## Examples
457
+
458
+ ### Complete CRUD Example
459
+
460
+ ```typescript
461
+ import { Controller, Get, Post, Put, Delete, Body, Param } from '@hazeljs/core';
462
+ import {
463
+ ApiTags,
464
+ ApiOperation,
465
+ ApiResponse,
466
+ ApiParam,
467
+ ApiBody,
468
+ ApiBearerAuth,
469
+ } from '@hazeljs/swagger';
470
+
471
+ @Controller('/products')
472
+ @ApiTags('Products')
473
+ @ApiBearerAuth()
474
+ export class ProductController {
475
+ @Get()
476
+ @ApiOperation({ summary: 'Get all products' })
477
+ @ApiResponse({
478
+ status: 200,
479
+ description: 'List of products',
480
+ type: [ProductDto],
481
+ })
482
+ findAll() {
483
+ return this.productService.findAll();
484
+ }
485
+
486
+ @Get('/:id')
487
+ @ApiOperation({ summary: 'Get product by ID' })
488
+ @ApiParam({ name: 'id', description: 'Product ID' })
489
+ @ApiResponse({ status: 200, description: 'Product found', type: ProductDto })
490
+ @ApiResponse({ status: 404, description: 'Product not found' })
491
+ findOne(@Param('id') id: string) {
492
+ return this.productService.findOne(id);
493
+ }
494
+
495
+ @Post()
496
+ @ApiOperation({ summary: 'Create a new product' })
497
+ @ApiBody({ type: CreateProductDto })
498
+ @ApiResponse({ status: 201, description: 'Product created', type: ProductDto })
499
+ @ApiResponse({ status: 400, description: 'Invalid input' })
500
+ create(@Body() createProductDto: CreateProductDto) {
501
+ return this.productService.create(createProductDto);
502
+ }
503
+
504
+ @Put('/:id')
505
+ @ApiOperation({ summary: 'Update a product' })
506
+ @ApiParam({ name: 'id', description: 'Product ID' })
507
+ @ApiBody({ type: UpdateProductDto })
508
+ @ApiResponse({ status: 200, description: 'Product updated', type: ProductDto })
509
+ @ApiResponse({ status: 404, description: 'Product not found' })
510
+ update(@Param('id') id: string, @Body() updateProductDto: UpdateProductDto) {
511
+ return this.productService.update(id, updateProductDto);
512
+ }
513
+
514
+ @Delete('/:id')
515
+ @ApiOperation({ summary: 'Delete a product' })
516
+ @ApiParam({ name: 'id', description: 'Product ID' })
517
+ @ApiResponse({ status: 200, description: 'Product deleted' })
518
+ @ApiResponse({ status: 404, description: 'Product not found' })
519
+ delete(@Param('id') id: string) {
520
+ return this.productService.delete(id);
521
+ }
522
+ }
523
+ ```
524
+
525
+ ## Export OpenAPI Spec
526
+
527
+ ### JSON Format
528
+
529
+ ```typescript
530
+ import { SwaggerModule } from '@hazeljs/swagger';
531
+
532
+ const document = SwaggerModule.createDocument(app);
533
+ const json = JSON.stringify(document, null, 2);
534
+
535
+ // Save to file
536
+ fs.writeFileSync('./openapi.json', json);
537
+ ```
538
+
539
+ ### YAML Format
540
+
541
+ ```typescript
542
+ import { SwaggerModule } from '@hazeljs/swagger';
543
+ import * as yaml from 'js-yaml';
544
+
545
+ const document = SwaggerModule.createDocument(app);
546
+ const yamlString = yaml.dump(document);
547
+
548
+ // Save to file
549
+ fs.writeFileSync('./openapi.yaml', yamlString);
550
+ ```
551
+
552
+ ## Best Practices
553
+
554
+ 1. **Document All Endpoints** - Add decorators to every route
555
+ 2. **Use DTOs** - Define request/response types with `@ApiProperty`
556
+ 3. **Add Examples** - Include realistic examples in documentation
557
+ 4. **Group by Tags** - Organize endpoints with `@ApiTags`
558
+ 5. **Document Errors** - Include all possible error responses
559
+ 6. **Security** - Document authentication requirements
560
+ 7. **Versioning** - Update version number with API changes
561
+ 8. **External Docs** - Link to additional documentation
562
+
563
+ ## Testing
564
+
565
+ ```bash
566
+ npm test
567
+ ```
568
+
569
+ ## Contributing
570
+
571
+ Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
572
+
573
+ ## License
574
+
575
+ Apache 2.0 © [HazelJS](https://hazeljs.com)
576
+
577
+ ## Links
578
+
579
+ - [Documentation](https://hazeljs.com/docs/packages/swagger)
580
+ - [OpenAPI Specification](https://swagger.io/specification/)
581
+ - [Swagger UI](https://swagger.io/tools/swagger-ui/)
582
+ - [GitHub](https://github.com/hazel-js/hazeljs)
583
+ - [Issues](https://github.com/hazel-js/hazeljs/issues)
584
+ - [Discord](https://discord.com/channels/1448263814238965833/1448263814859456575)
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @hazeljs/swagger - Swagger/OpenAPI module for HazelJS
3
+ */
4
+ export { SwaggerModule } from './swagger.module';
5
+ export { SwaggerService, type SwaggerSpec } from './swagger.service';
6
+ export { Swagger, ApiOperation, getSwaggerMetadata, getOperationMetadata, } from './swagger.decorator';
7
+ export type { SwaggerOptions, SwaggerOperation, SwaggerSchema } from './swagger.types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EACL,OAAO,EACP,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * @hazeljs/swagger - Swagger/OpenAPI module for HazelJS
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getOperationMetadata = exports.getSwaggerMetadata = exports.ApiOperation = exports.Swagger = exports.SwaggerService = exports.SwaggerModule = void 0;
7
+ var swagger_module_1 = require("./swagger.module");
8
+ Object.defineProperty(exports, "SwaggerModule", { enumerable: true, get: function () { return swagger_module_1.SwaggerModule; } });
9
+ var swagger_service_1 = require("./swagger.service");
10
+ Object.defineProperty(exports, "SwaggerService", { enumerable: true, get: function () { return swagger_service_1.SwaggerService; } });
11
+ var swagger_decorator_1 = require("./swagger.decorator");
12
+ Object.defineProperty(exports, "Swagger", { enumerable: true, get: function () { return swagger_decorator_1.Swagger; } });
13
+ Object.defineProperty(exports, "ApiOperation", { enumerable: true, get: function () { return swagger_decorator_1.ApiOperation; } });
14
+ Object.defineProperty(exports, "getSwaggerMetadata", { enumerable: true, get: function () { return swagger_decorator_1.getSwaggerMetadata; } });
15
+ Object.defineProperty(exports, "getOperationMetadata", { enumerable: true, get: function () { return swagger_decorator_1.getOperationMetadata; } });
@@ -0,0 +1,12 @@
1
+ import { SwaggerService } from './swagger.service';
2
+ import { RequestContext, Type } from '@hazeljs/core';
3
+ import { SwaggerSpec } from './swagger.service';
4
+ export declare class SwaggerController {
5
+ private swaggerService;
6
+ private static rootModule;
7
+ constructor(swaggerService: SwaggerService);
8
+ static setRootModule(module: Type<unknown>): void;
9
+ getSpec(_context: RequestContext): Promise<SwaggerSpec>;
10
+ getDocs(_context: RequestContext): Promise<string>;
11
+ }
12
+ //# sourceMappingURL=swagger.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swagger.controller.d.ts","sourceRoot":"","sources":["../src/swagger.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAIrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,qBAca,iBAAiB;IAGhB,OAAO,CAAC,cAAc;IAFlC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAgB;gBAErB,cAAc,EAAE,cAAc;IAElD,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI;IA6B3C,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAmIvD,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CA6DzD"}