@hazeljs/swagger 0.8.6 → 0.8.7

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.
@@ -1,36 +1,23 @@
1
- import { SwaggerOperation, SwaggerSchema } from './swagger.types';
1
+ import type { SwaggerBuildOptions, SwaggerSpec } from './swagger.types';
2
2
  import { Type } from '@hazeljs/core';
3
- export interface AutoSwaggerOptions {
4
- title?: string;
5
- description?: string;
6
- version?: string;
7
- autoGenerateOperations?: boolean;
8
- }
9
- export interface SwaggerSpec {
10
- openapi: string;
11
- info: {
12
- title?: string;
13
- description?: string;
14
- version?: string;
15
- };
16
- paths: Record<string, Record<string, SwaggerOperation>>;
17
- components: {
18
- schemas: Record<string, SwaggerSchema>;
19
- };
20
- tags?: Array<{
21
- name: string;
22
- description: string;
23
- }>;
24
- }
25
3
  export declare class SwaggerService {
26
4
  private spec;
27
- generateAutoSpec(moduleType: Type<unknown>, options?: AutoSwaggerOptions): SwaggerSpec;
28
- private processControllerAuto;
29
- private processRouteAuto;
5
+ /** Build spec by walking the module tree (imports + controllers). */
6
+ generateAutoSpec(moduleType: Type<unknown>, options?: SwaggerBuildOptions): SwaggerSpec;
7
+ /**
8
+ * Build spec from an explicit controller list.
9
+ * Controllers do not require `@Swagger` on the class; routes without `@ApiOperation`
10
+ * are filled when `autoGenerateOperations` is true (default).
11
+ */
12
+ generateSpec(controllers: Type<unknown>[], options?: SwaggerBuildOptions): SwaggerSpec;
13
+ private buildOpenApiFromControllers;
14
+ private normalizePrefix;
15
+ private joinPathSegments;
16
+ private processControllerRoutes;
17
+ private processRoute;
30
18
  private generateAutoOperation;
31
19
  private extractPathParameters;
32
20
  private addDefaultSchemas;
33
- generateSpec(controllers: Type<unknown>[]): SwaggerSpec;
34
21
  private normalizePath;
35
22
  }
36
23
  //# sourceMappingURL=swagger.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"swagger.service.d.ts","sourceRoot":"","sources":["../src/swagger.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGrC,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACxD,UAAU,EAAE;QACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;KACxC,CAAC;IACF,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAQD,qBACa,cAAc;IACzB,OAAO,CAAC,IAAI,CAOV;IAGF,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW;IAqCtF,OAAO,CAAC,qBAAqB;IA8B7B,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,qBAAqB;IAkG7B,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,iBAAiB;IAyCzB,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,WAAW;IAsGvD,OAAO,CAAC,aAAa;CAStB"}
1
+ {"version":3,"file":"swagger.service.d.ts","sourceRoot":"","sources":["../src/swagger.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EAGnB,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AASrC,qBACa,cAAc;IACzB,OAAO,CAAC,IAAI,CAOV;IAEF,qEAAqE;IACrE,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,WAAW;IAWvF;;;;OAIG;IACH,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,WAAW;IAoBtF,OAAO,CAAC,2BAA2B;IAgFnC,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,uBAAuB;IA2B/B,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,qBAAqB;IA+F7B,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,iBAAiB;IAwCzB,OAAO,CAAC,aAAa;CAOtB"}
@@ -25,76 +25,144 @@ let SwaggerService = class SwaggerService {
25
25
  },
26
26
  };
27
27
  }
28
- // Auto-generate spec from module without explicit Swagger decorators
28
+ /** Build spec by walking the module tree (imports + controllers). */
29
29
  generateAutoSpec(moduleType, options) {
30
30
  try {
31
31
  core_2.default.debug('Auto-generating Swagger spec from module:', moduleType.name);
32
- // Reset spec
33
- this.spec = {
34
- openapi: '3.0.0',
35
- info: {
36
- title: options?.title || 'HazelJS API',
37
- description: options?.description || 'Auto-generated API documentation',
38
- version: options?.version || '1.0.0',
39
- },
40
- paths: {},
41
- components: {
42
- schemas: {},
43
- },
44
- tags: [],
45
- };
46
32
  const controllers = (0, core_3.collectControllersFromModule)(moduleType);
47
- // Process each controller
48
- controllers.forEach((controller) => {
49
- this.processControllerAuto(controller, options?.autoGenerateOperations !== false);
50
- });
51
- // Add default error schemas
52
- this.addDefaultSchemas();
53
- core_2.default.debug('Auto-generated Swagger specification completed');
54
- return this.spec;
33
+ return this.buildOpenApiFromControllers(controllers, options);
55
34
  }
56
35
  catch (error) {
57
36
  core_2.default.error('Failed to auto-generate Swagger specification:', error);
58
37
  throw error;
59
38
  }
60
39
  }
61
- processControllerAuto(controller, autoGenerateOps) {
62
- if (!controller)
63
- return;
64
- // Get controller path from metadata
40
+ /**
41
+ * Build spec from an explicit controller list.
42
+ * Controllers do not require `@Swagger` on the class; routes without `@ApiOperation`
43
+ * are filled when `autoGenerateOperations` is true (default).
44
+ */
45
+ generateSpec(controllers, options) {
46
+ try {
47
+ if (!Array.isArray(controllers)) {
48
+ throw new Error('Controllers must be an array');
49
+ }
50
+ core_2.default.debug('Generating spec for controllers:', controllers.map((c) => c?.name || 'undefined'));
51
+ return this.buildOpenApiFromControllers(controllers, options);
52
+ }
53
+ catch (error) {
54
+ if (process.env.NODE_ENV !== 'test') {
55
+ core_2.default.error('Failed to generate Swagger specification:', error);
56
+ }
57
+ throw error;
58
+ }
59
+ }
60
+ buildOpenApiFromControllers(controllers, options = {}) {
61
+ const autoGenerateOps = options.autoGenerateOperations !== false;
62
+ this.spec = {
63
+ openapi: '3.0.0',
64
+ info: {},
65
+ paths: {},
66
+ components: {
67
+ schemas: {},
68
+ },
69
+ };
70
+ if (options.title !== undefined) {
71
+ this.spec.info.title = options.title;
72
+ }
73
+ if (options.description !== undefined) {
74
+ this.spec.info.description = options.description;
75
+ }
76
+ if (options.version !== undefined) {
77
+ this.spec.info.version = options.version;
78
+ }
79
+ if (options.servers?.length) {
80
+ this.spec.servers = options.servers;
81
+ }
82
+ if (options.securitySchemes && Object.keys(options.securitySchemes).length > 0) {
83
+ this.spec.components.securitySchemes = { ...options.securitySchemes };
84
+ }
85
+ if (options.security?.length) {
86
+ this.spec.security = options.security;
87
+ }
88
+ this.addDefaultSchemas();
89
+ const pathPrefix = this.normalizePrefix(options.globalPrefix);
90
+ for (const controller of controllers) {
91
+ if (!controller || typeof controller !== 'function') {
92
+ if (process.env.NODE_ENV !== 'test') {
93
+ core_2.default.warn('Invalid controller found:', controller);
94
+ }
95
+ continue;
96
+ }
97
+ const swaggerOptions = (0, swagger_decorator_1.getSwaggerMetadata)(controller.prototype);
98
+ if (swaggerOptions) {
99
+ if (!this.spec.info.title) {
100
+ this.spec.info = {
101
+ title: swaggerOptions.title,
102
+ description: swaggerOptions.description,
103
+ version: swaggerOptions.version,
104
+ };
105
+ }
106
+ if (swaggerOptions.tags && !this.spec.tags?.length) {
107
+ this.spec.tags = swaggerOptions.tags;
108
+ }
109
+ }
110
+ this.processControllerRoutes(controller, pathPrefix, autoGenerateOps);
111
+ }
112
+ if (!this.spec.info.title) {
113
+ this.spec.info.title = 'HazelJS API';
114
+ }
115
+ if (!this.spec.info.description) {
116
+ this.spec.info.description = 'API documentation';
117
+ }
118
+ if (!this.spec.info.version) {
119
+ this.spec.info.version = '1.0.0';
120
+ }
121
+ core_2.default.debug('Generated Swagger specification:', this.spec);
122
+ return this.spec;
123
+ }
124
+ normalizePrefix(prefix) {
125
+ if (!prefix)
126
+ return '';
127
+ let p = prefix.startsWith('/') ? prefix : `/${prefix}`;
128
+ p = p.replace(/\/$/, '');
129
+ return p;
130
+ }
131
+ joinPathSegments(...segments) {
132
+ const parts = [];
133
+ for (const seg of segments) {
134
+ if (!seg)
135
+ continue;
136
+ for (const piece of String(seg).split('/')) {
137
+ if (piece)
138
+ parts.push(piece);
139
+ }
140
+ }
141
+ return parts.length ? `/${parts.join('/')}` : '';
142
+ }
143
+ processControllerRoutes(controller, pathPrefix, autoGenerateOps) {
65
144
  const controllerMetadata = Reflect.getMetadata('hazel:controller', controller) || {};
66
145
  const basePath = controllerMetadata.path || '';
67
- // Get API tags from metadata
68
146
  const apiTags = Reflect.getMetadata('hazel:api:tags', controller) || [];
69
- // Add controller as a tag if not already present
70
- if (!this.spec.tags)
71
- this.spec.tags = [];
72
147
  const controllerTag = {
73
148
  name: apiTags.length > 0 ? apiTags[0] : controller.name,
74
149
  description: `${controller.name} endpoints`,
75
150
  };
76
- if (!this.spec.tags.find((tag) => tag.name === controllerTag.name)) {
77
- this.spec.tags.push(controllerTag);
78
- }
79
- // Get route metadata
80
151
  const routes = Reflect.getMetadata('hazel:routes', controller) || [];
81
- // Process each route
82
- routes.forEach((route) => {
83
- this.processRouteAuto(controller, route, basePath, controllerTag.name, autoGenerateOps);
84
- });
152
+ for (const route of routes) {
153
+ this.processRoute(controller, route, basePath, pathPrefix, controllerTag.name, autoGenerateOps);
154
+ }
85
155
  }
86
- processRouteAuto(controller, route, basePath, tag, autoGenerateOps) {
156
+ processRoute(controller, route, basePath, pathPrefix, tag, autoGenerateOps) {
87
157
  const { path, method, propertyKey } = route;
88
- const fullPath = this.normalizePath(`${basePath}${path}`);
89
- // Check for existing operation metadata
158
+ const fullPath = this.normalizePath(this.joinPathSegments(pathPrefix, basePath, path));
159
+ const pathForParams = this.joinPathSegments(basePath, path);
90
160
  let operation = (0, swagger_decorator_1.getOperationMetadata)(controller.prototype, propertyKey);
91
- // Auto-generate operation if not found and auto-generation is enabled
92
161
  if (!operation && autoGenerateOps) {
93
- operation = this.generateAutoOperation(method, propertyKey, tag, route);
162
+ operation = this.generateAutoOperation(method, propertyKey, tag, pathForParams);
94
163
  }
95
164
  if (!operation)
96
165
  return;
97
- // Add operation to paths
98
166
  const pathItem = this.spec.paths[fullPath] || {};
99
167
  pathItem[method.toLowerCase()] = {
100
168
  ...operation,
@@ -102,18 +170,19 @@ let SwaggerService = class SwaggerService {
102
170
  };
103
171
  this.spec.paths[fullPath] = pathItem;
104
172
  }
105
- generateAutoOperation(method, propertyKey, tag, route) {
173
+ generateAutoOperation(method, propertyKey, tag, pathForParams) {
106
174
  const methodName = String(propertyKey);
107
- const isGetMethod = method.toLowerCase() === 'get';
108
- const isPostMethod = method.toLowerCase() === 'post';
109
- const isPutMethod = method.toLowerCase() === 'put';
110
- const isDeleteMethod = method.toLowerCase() === 'delete';
111
- // Generate summary based on method name
175
+ const m = method.toLowerCase();
176
+ const isGetMethod = m === 'get';
177
+ const isPostMethod = m === 'post';
178
+ const isPutMethod = m === 'put';
179
+ const isPatchMethod = m === 'patch';
180
+ const isDeleteMethod = m === 'delete';
112
181
  let summary = '';
113
182
  if (methodName.includes('create') || isPostMethod) {
114
183
  summary = `Create new resource`;
115
184
  }
116
- else if (methodName.includes('update') || isPutMethod) {
185
+ else if (methodName.includes('update') || isPutMethod || isPatchMethod) {
117
186
  summary = `Update resource`;
118
187
  }
119
188
  else if (methodName.includes('delete') || isDeleteMethod) {
@@ -125,6 +194,7 @@ let SwaggerService = class SwaggerService {
125
194
  else {
126
195
  summary = `${method.toUpperCase()} ${methodName}`;
127
196
  }
197
+ const errorSchema = { $ref: '#/components/schemas/Error' };
128
198
  const operation = {
129
199
  summary,
130
200
  description: `Auto-generated ${method.toUpperCase()} operation`,
@@ -145,10 +215,25 @@ let SwaggerService = class SwaggerService {
145
215
  },
146
216
  },
147
217
  },
218
+ '400': {
219
+ description: 'Bad request',
220
+ content: {
221
+ 'application/json': {
222
+ schema: errorSchema,
223
+ },
224
+ },
225
+ },
226
+ '500': {
227
+ description: 'Internal server error',
228
+ content: {
229
+ 'application/json': {
230
+ schema: errorSchema,
231
+ },
232
+ },
233
+ },
148
234
  },
149
235
  };
150
- // Add parameters for path variables - need to extract from the route path
151
- const pathParams = this.extractPathParameters(route.path);
236
+ const pathParams = this.extractPathParameters(pathForParams);
152
237
  if (pathParams.length > 0) {
153
238
  operation.parameters = pathParams.map((param) => ({
154
239
  name: param,
@@ -157,8 +242,7 @@ let SwaggerService = class SwaggerService {
157
242
  schema: { type: 'string' },
158
243
  }));
159
244
  }
160
- // Add request body for POST/PUT/PATCH
161
- if (isPostMethod || isPutMethod) {
245
+ if (isPostMethod || isPutMethod || isPatchMethod) {
162
246
  operation.requestBody = {
163
247
  required: true,
164
248
  content: {
@@ -170,38 +254,18 @@ let SwaggerService = class SwaggerService {
170
254
  },
171
255
  };
172
256
  }
173
- // Add common error responses
174
- if (operation.responses) {
175
- operation.responses['400'] = {
176
- description: 'Bad request',
177
- content: {
178
- 'application/json': {
179
- schema: { type: 'object' },
180
- },
181
- },
182
- };
183
- operation.responses['500'] = {
184
- description: 'Internal server error',
185
- content: {
186
- 'application/json': {
187
- schema: { type: 'object' },
188
- },
189
- },
190
- };
191
- }
192
257
  return operation;
193
258
  }
194
- extractPathParameters(path) {
259
+ extractPathParameters(routePath) {
195
260
  const params = [];
196
261
  const paramRegex = /:([^/]+)/g;
197
262
  let match;
198
- while ((match = paramRegex.exec(path)) !== null) {
263
+ while ((match = paramRegex.exec(routePath)) !== null) {
199
264
  params.push(match[1]);
200
265
  }
201
266
  return params;
202
267
  }
203
268
  addDefaultSchemas() {
204
- // Add common error schemas
205
269
  this.spec.components.schemas.Error = {
206
270
  type: 'object',
207
271
  properties: {
@@ -239,95 +303,12 @@ let SwaggerService = class SwaggerService {
239
303
  },
240
304
  };
241
305
  }
242
- generateSpec(controllers) {
243
- try {
244
- if (!Array.isArray(controllers)) {
245
- throw new Error('Controllers must be an array');
246
- }
247
- core_2.default.debug('Generating spec for controllers:', controllers.map((c) => c?.name || 'undefined'));
248
- // Reset spec
249
- this.spec = {
250
- openapi: '3.0.0',
251
- info: {},
252
- paths: {},
253
- components: {
254
- schemas: {},
255
- },
256
- };
257
- // Process each controller
258
- controllers.forEach((controller) => {
259
- if (!controller || typeof controller !== 'function') {
260
- if (process.env.NODE_ENV !== 'test') {
261
- core_2.default.warn('Invalid controller found:', controller);
262
- }
263
- return;
264
- }
265
- // Get Swagger metadata from the controller prototype
266
- const swaggerOptions = (0, swagger_decorator_1.getSwaggerMetadata)(controller.prototype);
267
- if (!swaggerOptions) {
268
- core_2.default.debug(`No Swagger metadata found for controller: ${controller.name}`);
269
- return;
270
- }
271
- core_2.default.debug(`Processing controller: ${controller.name}`, swaggerOptions);
272
- // Update info if not already set
273
- if (!this.spec.info.title) {
274
- this.spec.info = {
275
- title: swaggerOptions.title,
276
- description: swaggerOptions.description,
277
- version: swaggerOptions.version,
278
- };
279
- }
280
- // Add tags if not already set
281
- if (swaggerOptions.tags && !this.spec.tags) {
282
- this.spec.tags = swaggerOptions.tags;
283
- }
284
- // Get controller path from metadata
285
- const controllerMetadata = Reflect.getMetadata('hazel:controller', controller) || {};
286
- const basePath = controllerMetadata.path || '';
287
- // Get route metadata
288
- const routes = Reflect.getMetadata('hazel:routes', controller);
289
- if (!routes) {
290
- core_2.default.debug(`No routes found for controller: ${controller.name}`);
291
- return;
292
- }
293
- core_2.default.debug(`Found routes for ${controller.name}:`, routes);
294
- // Process each route
295
- routes.forEach((route) => {
296
- const { path, method, propertyKey } = route;
297
- const fullPath = this.normalizePath(`${basePath}${path}`);
298
- const operation = (0, swagger_decorator_1.getOperationMetadata)(controller.prototype, propertyKey);
299
- if (!operation) {
300
- core_2.default.debug(`No operation metadata found for method: ${String(propertyKey)}`);
301
- return;
302
- }
303
- core_2.default.debug(`Adding operation for ${method} ${fullPath}`);
304
- // Add operation to paths
305
- const pathItem = this.spec.paths[fullPath] || {};
306
- pathItem[method.toLowerCase()] = {
307
- ...operation,
308
- tags: operation.tags || [controller.name],
309
- };
310
- this.spec.paths[fullPath] = pathItem;
311
- });
312
- });
313
- core_2.default.debug('Generated Swagger specification:', this.spec);
314
- return this.spec;
315
- }
316
- catch (error) {
317
- if (process.env.NODE_ENV !== 'test') {
318
- core_2.default.error('Failed to generate Swagger specification:', error);
319
- }
320
- throw error;
321
- }
322
- }
323
306
  normalizePath(path) {
324
- // Remove trailing slash
325
307
  let normalized = path.replace(/\/$/, '');
326
- // Ensure path starts with slash
327
308
  if (!normalized.startsWith('/')) {
328
309
  normalized = '/' + normalized;
329
310
  }
330
- return normalized;
311
+ return normalized || '/';
331
312
  }
332
313
  };
333
314
  exports.SwaggerService = SwaggerService;