@avleon/core 0.0.44 → 0.0.46

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 (58) hide show
  1. package/License +21 -21
  2. package/README.md +667 -681
  3. package/dist/application.test.js +15 -0
  4. package/dist/controller.test.js +0 -14
  5. package/dist/core/application.d.ts +74 -0
  6. package/dist/core/application.js +424 -0
  7. package/dist/core/router.d.ts +44 -0
  8. package/dist/core/router.js +520 -0
  9. package/dist/core/testing.d.ts +21 -0
  10. package/dist/core/testing.js +104 -0
  11. package/dist/core/types.d.ts +67 -0
  12. package/dist/core/types.js +2 -0
  13. package/dist/event-dispatcher.d.ts +0 -1
  14. package/dist/event-dispatcher.js +4 -7
  15. package/dist/file-storage.test.js +15 -2
  16. package/dist/helpers.d.ts +9 -42
  17. package/dist/helpers.js +19 -411
  18. package/dist/index.d.ts +17 -15
  19. package/dist/index.js +18 -22
  20. package/dist/interfaces/avleon-application.d.ts +74 -26
  21. package/dist/interfaces/avleon-application.js +1 -0
  22. package/dist/middleware.d.ts +11 -4
  23. package/dist/middleware.js +9 -0
  24. package/dist/multipart.d.ts +2 -2
  25. package/dist/openapi.d.ts +70 -3
  26. package/dist/openapi.js +32 -0
  27. package/dist/params.js +1 -6
  28. package/dist/params.test.js +8 -8
  29. package/dist/queue.d.ts +27 -36
  30. package/dist/queue.js +67 -99
  31. package/dist/route-methods.js +16 -5
  32. package/dist/swagger-schema.d.ts +11 -17
  33. package/dist/swagger-schema.js +84 -82
  34. package/dist/swagger-schema.test.js +32 -12
  35. package/dist/utils/common-utils.d.ts +17 -0
  36. package/dist/utils/common-utils.js +108 -0
  37. package/dist/utils/di-utils.d.ts +1 -0
  38. package/dist/utils/di-utils.js +22 -0
  39. package/dist/utils/hash.d.ts +0 -2
  40. package/dist/utils/hash.js +1 -5
  41. package/dist/utils/object-utils.d.ts +11 -0
  42. package/dist/utils/object-utils.js +198 -0
  43. package/dist/utils/validation-utils.d.ts +13 -0
  44. package/dist/utils/validation-utils.js +119 -0
  45. package/dist/validation.js +1 -4
  46. package/dist/websocket.d.ts +3 -0
  47. package/dist/websocket.js +2 -1
  48. package/package.json +53 -39
  49. package/dist/application.d.ts +0 -47
  50. package/dist/application.js +0 -50
  51. package/dist/icore.d.ts +0 -226
  52. package/dist/icore.js +0 -968
  53. package/dist/icore.test.js +0 -14
  54. package/dist/queue.test.d.ts +0 -1
  55. package/dist/queue.test.js +0 -79
  56. package/dist/testing.d.ts +0 -55
  57. package/dist/testing.js +0 -196
  58. /package/dist/{icore.test.d.ts → application.test.d.ts} +0 -0
package/dist/queue.js CHANGED
@@ -1,116 +1,84 @@
1
1
  "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
4
  };
8
5
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.QueueManager = exports.AvleonQueue = exports.FileQueueAdapter = void 0;
10
- const fs_1 = require("fs");
11
- const path_1 = require("path");
12
- const crypto_1 = require("crypto");
13
- const events_1 = require("events");
14
- const decorators_1 = require("./decorators");
15
- class FileQueueAdapter {
16
- constructor(queueName) {
17
- this.queueFile = (0, path_1.join)(__dirname, `${queueName}.json`);
18
- }
19
- async loadJobs() {
20
- try {
21
- const data = await fs_1.promises.readFile(this.queueFile, "utf-8");
22
- return JSON.parse(data);
6
+ exports.AvleonQueue = void 0;
7
+ exports.Queue = Queue;
8
+ const bull_1 = __importDefault(require("bull"));
9
+ const typedi_1 = require("typedi");
10
+ class AvleonQueue {
11
+ constructor(name, adapter, handler) {
12
+ this.name = name;
13
+ this.adapter = adapter;
14
+ // Initialize queue with adapter or default Redis connection
15
+ this.queue = new bull_1.default(name || 'default', adapter);
16
+ this.handlerFn = handler;
17
+ // Check if the instance has a handler method defined
18
+ // This allows subclasses to define handler as a method
19
+ if (typeof this.handler === 'function' && !this.handlerFn) {
20
+ this.handlerFn = (job) => this.handler(job);
23
21
  }
24
- catch (_a) {
25
- return [];
22
+ // If handler is provided (from decorator or class method), set up processing
23
+ if (this.handlerFn) {
24
+ this.queue.process(this.handlerFn);
26
25
  }
27
26
  }
28
- async saveJobs(jobs) {
29
- await fs_1.promises.writeFile(this.queueFile, JSON.stringify(jobs, null, 2), "utf-8");
27
+ // Add job to queue
28
+ add(data, options) {
29
+ return this.queue.add(data, options);
30
30
  }
31
- }
32
- exports.FileQueueAdapter = FileQueueAdapter;
33
- class AvleonQueue extends events_1.EventEmitter {
34
- constructor(name, adapter, jobHandler) {
35
- super();
36
- this.processing = false;
37
- this.stopped = false;
38
- this.name = name;
39
- this.adapter = adapter ? adapter : new FileQueueAdapter(name);
40
- this.jobHandler = jobHandler || this.defaultHandler.bind(this);
41
- this.setMaxListeners(10);
31
+ // Add job with delay
32
+ delay(data, delayMs, options) {
33
+ return this.queue.add(data, { ...options, delay: delayMs });
42
34
  }
43
- async defaultHandler(job) {
44
- if (typeof job.data === "function") {
45
- await job.data();
46
- }
35
+ // Process jobs (can be called manually if not using handler)
36
+ process(handler) {
37
+ this.handlerFn = handler;
38
+ this.queue.process(handler);
47
39
  }
48
- async addJob(data, options) {
49
- const job = {
50
- id: (0, crypto_1.randomUUID)(),
51
- data,
52
- runAt: Date.now() + ((options === null || options === void 0 ? void 0 : options.delay) || 0),
53
- status: "pending",
54
- };
55
- const jobs = await this.adapter.loadJobs();
56
- jobs.push(job);
57
- await this.adapter.saveJobs(jobs);
58
- if (!this.processing)
59
- this.processNext();
40
+ // Process with concurrency
41
+ processConcurrent(concurrency, handler) {
42
+ this.handlerFn = handler;
43
+ this.queue.process(concurrency, handler);
60
44
  }
61
- async processNext() {
62
- if (this.processing || this.stopped)
63
- return;
64
- this.processing = true;
65
- while (!this.stopped) {
66
- const jobs = await this.adapter.loadJobs();
67
- const nextJob = jobs.find((j) => j.status === "pending");
68
- if (!nextJob) {
69
- this.processing = false;
70
- return;
71
- }
72
- const now = Date.now();
73
- if (nextJob.runAt && nextJob.runAt > now) {
74
- const delay = nextJob.runAt - now;
75
- await new Promise((res) => setTimeout(res, delay));
76
- }
77
- nextJob.status = "running";
78
- await this.adapter.saveJobs(jobs);
79
- this.emit("start", nextJob);
80
- try {
81
- await this.jobHandler(nextJob);
82
- nextJob.status = "completed";
83
- this.emit("done", nextJob);
84
- }
85
- catch (err) {
86
- nextJob.status = "failed";
87
- this.emit("failed", err, nextJob);
88
- }
89
- await this.adapter.saveJobs(jobs.filter((j) => j.id !== nextJob.id));
90
- }
91
- this.processing = false;
45
+ // Get the underlying Bull queue
46
+ getQueue() {
47
+ return this.queue;
48
+ }
49
+ async clean(grace, status) {
50
+ return this.queue.clean(grace, status);
92
51
  }
93
- async onDone(cb) {
94
- this.on("done", cb);
52
+ async close() {
53
+ await this.queue.close();
95
54
  }
96
- async onFailed(cb) {
97
- this.on("failed", cb);
55
+ async pause() {
56
+ await this.queue.pause();
98
57
  }
99
- async getJobs() {
100
- return this.adapter.loadJobs();
58
+ async resume() {
59
+ await this.queue.resume();
101
60
  }
102
- async stop() {
103
- this.stopped = true;
61
+ async getJob(jobId) {
62
+ return this.queue.getJob(jobId);
63
+ }
64
+ async getJobs(types, start, end) {
65
+ return this.queue.getJobs(types, start, end);
104
66
  }
105
67
  }
106
68
  exports.AvleonQueue = AvleonQueue;
107
- let QueueManager = class QueueManager {
108
- async from(name, jobHandler) {
109
- const q = new AvleonQueue(name, new FileQueueAdapter(name), jobHandler);
110
- return q;
111
- }
112
- };
113
- exports.QueueManager = QueueManager;
114
- exports.QueueManager = QueueManager = __decorate([
115
- decorators_1.AppService
116
- ], QueueManager);
69
+ function Queue(config) {
70
+ return function (target) {
71
+ // Create a new class that extends the target
72
+ const DecoratedClass = class extends target {
73
+ constructor(...args) {
74
+ super(config.name, config.adapter, config.handler);
75
+ }
76
+ };
77
+ Object.defineProperty(DecoratedClass, 'name', {
78
+ value: target.name,
79
+ writable: false
80
+ });
81
+ (0, typedi_1.Service)()(DecoratedClass);
82
+ return DecoratedClass;
83
+ };
84
+ }
@@ -17,17 +17,28 @@ function Route(method, pathOrOptions, maybeOptions) {
17
17
  let path = "/";
18
18
  let options = {};
19
19
  if (typeof pathOrOptions === "string") {
20
- path = pathOrOptions;
20
+ path = pathOrOptions || "/";
21
21
  options = maybeOptions || {};
22
22
  }
23
- else if (typeof pathOrOptions === "object") {
23
+ else if (typeof pathOrOptions === "object" && pathOrOptions !== null) {
24
24
  options = pathOrOptions;
25
- path = options.name || "/";
25
+ path = options.path || options.name || "/";
26
26
  }
27
- // Define metadata
27
+ else {
28
+ // @Get() called with no args
29
+ options = maybeOptions || {};
30
+ path = "/";
31
+ }
32
+ //Ensure path is always a string
33
+ path = typeof path === "string" ? path : "/";
28
34
  Reflect.defineMetadata("route:path", path, target, propertyKey);
29
35
  Reflect.defineMetadata("route:method", method, target, propertyKey);
30
- Reflect.defineMetadata(container_1.ROUTE_META_KEY, { ...options, method, path, controller: target.constructor.name }, target, propertyKey);
36
+ Reflect.defineMetadata(container_1.ROUTE_META_KEY, {
37
+ ...options,
38
+ method,
39
+ path,
40
+ controller: target.constructor.name,
41
+ }, target, propertyKey);
31
42
  if (options) {
32
43
  Reflect.defineMetadata("route:options", options, target, propertyKey);
33
44
  }
@@ -1,25 +1,19 @@
1
- export declare function OpenApiProperty(options?: {
2
- type?: any;
3
- description?: string;
4
- deprecated?: boolean;
5
- example?: any;
6
- enum?: any[];
7
- format?: string;
8
- default?: any;
9
- minimum?: number;
10
- maximum?: number;
11
- minLength?: number;
12
- maxLength?: number;
13
- pattern?: string;
14
- oneOf?: any[];
15
- allOf?: any[];
16
- anyOf?: any[];
1
+ import { ParamSchema } from "./openapi";
2
+ export declare function OpenApiProperty(options?: ParamSchema & {
17
3
  exclude?: boolean;
18
4
  isArray?: boolean;
19
5
  items?: Record<string, any>;
6
+ oneOf?: any[];
7
+ allOf?: any[];
8
+ anyOf?: any[];
9
+ readOnly?: boolean;
10
+ writeOnly?: boolean;
11
+ nullable?: boolean;
12
+ title?: string;
20
13
  }): (target: any, propertyKey: string) => void;
21
14
  export declare function CreateSwaggerObjectSchema(classType: any): any;
22
- export declare function generateSwaggerSchema(classType: any): any;
15
+ export declare function generateClassSchema(classType: any): any;
16
+ export declare function generateSwaggerSchema(controllers: any[]): any;
23
17
  export declare function OpenApiResponse(code: number | undefined, model: any, description?: string): {
24
18
  description: string;
25
19
  content: {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OpenApiProperty = OpenApiProperty;
4
4
  exports.CreateSwaggerObjectSchema = CreateSwaggerObjectSchema;
5
+ exports.generateClassSchema = generateClassSchema;
5
6
  exports.generateSwaggerSchema = generateSwaggerSchema;
6
7
  exports.OpenApiResponse = OpenApiResponse;
7
8
  /**
@@ -11,6 +12,7 @@ exports.OpenApiResponse = OpenApiResponse;
11
12
  * @url https://github.com/xtareq
12
13
  */
13
14
  const class_validator_1 = require("class-validator");
15
+ const container_1 = require("./container");
14
16
  // Decorator to add OpenAPI metadata to properties
15
17
  function OpenApiProperty(options) {
16
18
  return function (target, propertyKey) {
@@ -39,6 +41,7 @@ function OpenApiProperty(options) {
39
41
  }
40
42
  function extractOpenApiFields(meta) {
41
43
  const result = {};
44
+ // ✅ Complete list — was missing required, title, readOnly, writeOnly, nullable
42
45
  const jsonSchemaFields = [
43
46
  "description",
44
47
  "deprecated",
@@ -54,46 +57,28 @@ function extractOpenApiFields(meta) {
54
57
  "oneOf",
55
58
  "allOf",
56
59
  "anyOf",
60
+ "title",
61
+ "readOnly",
62
+ "writeOnly",
63
+ "nullable"
57
64
  ];
58
65
  // Valid JSON Schema formats
59
66
  const validFormats = [
60
- "date-time",
61
- "date",
62
- "time",
63
- "duration",
64
- "email",
65
- "idn-email",
66
- "hostname",
67
- "idn-hostname",
68
- "ipv4",
69
- "ipv6",
70
- "uri",
71
- "uri-reference",
72
- "iri",
73
- "iri-reference",
74
- "uuid",
75
- "uri-template",
76
- "json-pointer",
77
- "relative-json-pointer",
78
- "regex",
79
- "int32",
80
- "int64",
81
- "float",
82
- "double",
83
- "byte",
84
- "binary",
85
- "password",
67
+ "date-time", "date", "time", "duration",
68
+ "email", "idn-email", "hostname", "idn-hostname",
69
+ "ipv4", "ipv6", "uri", "uri-reference",
70
+ "iri", "iri-reference", "uuid", "uri-template",
71
+ "json-pointer", "relative-json-pointer", "regex",
72
+ "int32", "int64", "float", "double",
73
+ "byte", "binary", "password",
86
74
  ];
87
75
  jsonSchemaFields.forEach((field) => {
88
76
  if (meta[field] !== undefined) {
89
- // Validate format field
90
77
  if (field === "format") {
91
78
  const formatValue = meta[field];
92
- // Only add format if it's a valid format string
93
79
  if (validFormats.includes(formatValue)) {
94
80
  result[field] = formatValue;
95
81
  }
96
- // Skip invalid formats
97
82
  }
98
83
  else {
99
84
  result[field] = meta[field];
@@ -105,17 +90,13 @@ function extractOpenApiFields(meta) {
105
90
  function CreateSwaggerObjectSchema(classType) {
106
91
  const metadataStorage = (0, class_validator_1.getMetadataStorage)();
107
92
  const validationMetadata = metadataStorage.getTargetValidationMetadatas(classType, "", true, false);
108
- const schema = {
109
- type: "object",
110
- properties: {},
111
- required: [],
112
- };
93
+ const schema = { type: "object", properties: {}, required: [] };
113
94
  const prototype = classType.prototype;
114
95
  const propertyKeys = new Set();
115
- // Collect property names
116
96
  Object.getOwnPropertyNames(prototype).forEach((k) => propertyKeys.add(k));
117
97
  Object.keys(prototype).forEach((k) => propertyKeys.add(k));
118
98
  validationMetadata.forEach((m) => propertyKeys.add(m.propertyName));
99
+ // ✅ Discover instance-level properties (class fields with !)
119
100
  try {
120
101
  const instance = new classType();
121
102
  Reflect.ownKeys(instance).forEach((k) => {
@@ -128,11 +109,9 @@ function CreateSwaggerObjectSchema(classType) {
128
109
  var _a;
129
110
  if (!propertyName || propertyName === "constructor")
130
111
  return;
131
- // Get decorator metadata
132
112
  const openApiMeta = Reflect.getMetadata("property:openapi", prototype, propertyName);
133
113
  if (openApiMeta === null || openApiMeta === void 0 ? void 0 : openApiMeta.exclude)
134
114
  return;
135
- // Get TypeScript type
136
115
  const propertyType = Reflect.getMetadata("design:type", prototype, propertyName);
137
116
  let swaggerProperty = {};
138
117
  switch (propertyType) {
@@ -151,7 +130,7 @@ function CreateSwaggerObjectSchema(classType) {
151
130
  break;
152
131
  case Array:
153
132
  swaggerProperty.type = "array";
154
- swaggerProperty.items = { type: "string" }; // fallback
133
+ swaggerProperty.items = { type: "string" };
155
134
  break;
156
135
  case Object:
157
136
  swaggerProperty = CreateSwaggerObjectSchema(propertyType);
@@ -170,7 +149,6 @@ function CreateSwaggerObjectSchema(classType) {
170
149
  ...openApiMeta,
171
150
  ...extractOpenApiFields(openApiMeta),
172
151
  };
173
- // 🪄 Auto-handle file uploads
174
152
  if (openApiMeta.format === "binary") {
175
153
  if (openApiMeta.isArray || propertyType === Array) {
176
154
  swaggerProperty = {
@@ -190,7 +168,6 @@ function CreateSwaggerObjectSchema(classType) {
190
168
  }
191
169
  schema.properties[propertyName] = swaggerProperty;
192
170
  });
193
- // Handle validation decorators
194
171
  validationMetadata.forEach((meta) => {
195
172
  const propertyName = meta.propertyName;
196
173
  const property = schema.properties[propertyName];
@@ -199,9 +176,8 @@ function CreateSwaggerObjectSchema(classType) {
199
176
  switch (meta.name) {
200
177
  case "isNotEmpty":
201
178
  case "isDefined":
202
- if (!schema.required.includes(propertyName)) {
179
+ if (!schema.required.includes(propertyName))
203
180
  schema.required.push(propertyName);
204
- }
205
181
  break;
206
182
  case "isOptional":
207
183
  schema.required = schema.required.filter((item) => item !== propertyName);
@@ -241,24 +217,32 @@ function CreateSwaggerObjectSchema(classType) {
241
217
  break;
242
218
  }
243
219
  });
244
- if (schema.required.length === 0) {
220
+ if (schema.required.length === 0)
245
221
  delete schema.required;
246
- }
247
222
  return schema;
248
223
  }
249
- function generateSwaggerSchema(classType) {
224
+ function generateClassSchema(classType) {
225
+ const schema = { type: "object", properties: {}, required: [] };
226
+ // ✅ Guard against null/undefined
227
+ if (!classType || !classType.prototype)
228
+ return schema;
250
229
  const metadataStorage = (0, class_validator_1.getMetadataStorage)();
251
230
  const validationMetadata = metadataStorage.getTargetValidationMetadatas(classType, "", true, false);
252
- const schema = {
253
- type: "object",
254
- properties: {},
255
- required: [],
256
- };
257
231
  const prototype = classType.prototype;
258
232
  const propertyKeys = new Set([
259
233
  ...Object.getOwnPropertyNames(prototype),
260
234
  ...validationMetadata.map((m) => m.propertyName),
261
235
  ]);
236
+ // ✅ Discover instance-level class fields (e.g. `search!: string`)
237
+ // These don't appear on prototype — only on instantiated objects
238
+ try {
239
+ const instance = new classType();
240
+ Reflect.ownKeys(instance).forEach((k) => {
241
+ if (typeof k === "string")
242
+ propertyKeys.add(k);
243
+ });
244
+ }
245
+ catch (_) { }
262
246
  propertyKeys.forEach((propertyName) => {
263
247
  var _a;
264
248
  if (!propertyName || propertyName === "constructor")
@@ -284,36 +268,40 @@ function generateSwaggerSchema(classType) {
284
268
  break;
285
269
  case Array:
286
270
  swaggerProperty.type = "array";
287
- swaggerProperty.items = { type: "string" }; // fallback
271
+ swaggerProperty.items = { type: "string" };
288
272
  break;
289
273
  case Object:
290
- swaggerProperty = generateSwaggerSchema(propertyType);
274
+ swaggerProperty = generateClassSchema(propertyType);
291
275
  break;
292
276
  default:
293
- swaggerProperty.type = ((_a = propertyType === null || propertyType === void 0 ? void 0 : propertyType.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || "string";
277
+ if (propertyType && typeof propertyType === "function") {
278
+ swaggerProperty.$ref = `#/components/schemas/${propertyType.name}`;
279
+ }
280
+ else {
281
+ swaggerProperty.type = ((_a = propertyType === null || propertyType === void 0 ? void 0 : propertyType.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || "string";
282
+ }
294
283
  }
295
- // Apply OpenApi metadata if present
284
+ // Apply OpenApi metadata extractOpenApiFields now includes all fields
296
285
  if (openApiMeta) {
286
+ const { required: _required, exclude: _exclude, isArray: _isArray, ...safeOpenApiMeta } = openApiMeta;
297
287
  swaggerProperty = {
298
288
  ...swaggerProperty,
299
- ...extractOpenApiFields(openApiMeta),
289
+ ...extractOpenApiFields(safeOpenApiMeta),
300
290
  };
301
291
  }
302
292
  schema.properties[propertyName] = swaggerProperty;
303
293
  });
304
- // Handle validation rules
305
294
  validationMetadata.forEach((meta) => {
306
295
  const propertyName = meta.propertyName;
296
+ // Guard: property might not be in schema if it had no type info
297
+ if (!schema.properties[propertyName]) {
298
+ schema.properties[propertyName] = { type: "string" }; // ✅ safe fallback
299
+ }
307
300
  switch (meta.name) {
308
301
  case "isNotEmpty":
309
- if (!schema.required.includes(propertyName)) {
310
- schema.required.push(propertyName);
311
- }
312
- break;
313
302
  case "isDefined":
314
- if (!schema.required.includes(propertyName)) {
303
+ if (!schema.required.includes(propertyName))
315
304
  schema.required.push(propertyName);
316
- }
317
305
  break;
318
306
  case "isOptional":
319
307
  schema.required = schema.required.filter((item) => item !== propertyName);
@@ -353,20 +341,46 @@ function generateSwaggerSchema(classType) {
353
341
  break;
354
342
  }
355
343
  });
344
+ if (schema.required.length === 0)
345
+ delete schema.required;
356
346
  return schema;
357
347
  }
348
+ // Build OpenAPI components.schemas from an array of controller/DTO classes
349
+ function generateSwaggerSchema(controllers) {
350
+ if (!Array.isArray(controllers)) {
351
+ return generateClassSchema(controllers);
352
+ }
353
+ const components = {};
354
+ for (const controller of controllers) {
355
+ if (!controller || typeof controller !== "function")
356
+ continue;
357
+ if (!controller.prototype)
358
+ continue;
359
+ // Skip @ApiController classes
360
+ const isController = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, controller);
361
+ if (isController)
362
+ continue;
363
+ // Only include classes explicitly marked with @ApiSchema
364
+ const isSchema = Reflect.getMetadata("openapi:schema", controller);
365
+ if (!isSchema)
366
+ continue;
367
+ components[controller.name] = generateClassSchema(controller);
368
+ }
369
+ return {
370
+ components: Object.keys(components).length > 0
371
+ ? { schemas: components }
372
+ : undefined,
373
+ };
374
+ }
358
375
  function OpenApiResponse(code = 200, model, description = "Successful response") {
359
376
  let dataSchema;
360
377
  if (typeof model === "function") {
361
- // Class or constructor
362
- dataSchema = generateSwaggerSchema(model);
378
+ dataSchema = generateClassSchema(model);
363
379
  }
364
380
  else if (model && typeof model === "object") {
365
- // Example object
366
381
  dataSchema = inferSchemaFromExample(model);
367
382
  }
368
383
  else {
369
- // Fallback
370
384
  dataSchema = { type: "string" };
371
385
  }
372
386
  let message = "OK";
@@ -408,16 +422,10 @@ function OpenApiResponse(code = 200, model, description = "Successful response")
408
422
  },
409
423
  };
410
424
  }
411
- /**
412
- * Infer a basic JSON schema from a plain JavaScript object.
413
- */
414
425
  function inferSchemaFromExample(obj) {
415
426
  var _a;
416
427
  if (Array.isArray(obj)) {
417
- return {
418
- type: "array",
419
- items: inferSchemaFromExample((_a = obj[0]) !== null && _a !== void 0 ? _a : {}),
420
- };
428
+ return { type: "array", items: inferSchemaFromExample((_a = obj[0]) !== null && _a !== void 0 ? _a : {}) };
421
429
  }
422
430
  if (obj && typeof obj === "object") {
423
431
  const properties = {};
@@ -428,18 +436,12 @@ function inferSchemaFromExample(obj) {
428
436
  }
429
437
  return inferType(obj);
430
438
  }
431
- /**
432
- * Infer primitive schema type
433
- */
434
439
  function inferType(value) {
435
440
  const type = typeof value;
436
441
  switch (type) {
437
- case "string":
438
- return { type: "string", example: value };
439
- case "number":
440
- return { type: "number", example: value };
441
- case "boolean":
442
- return { type: "boolean", example: value };
442
+ case "string": return { type: "string", example: value };
443
+ case "number": return { type: "number", example: value };
444
+ case "boolean": return { type: "boolean", example: value };
443
445
  case "object":
444
446
  if (Array.isArray(value))
445
447
  return inferSchemaFromExample(value);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("reflect-metadata");
4
- const swagger_schema_1 = require("./swagger-schema");
4
+ const swagger_schema_1 = require("./swagger-schema"); // ✅ single-class function
5
5
  // Mocks for class-validator metadata
6
6
  const mockValidationMetadatas = [
7
7
  { propertyName: "name", name: "isNotEmpty", constraints: [] },
@@ -11,12 +11,21 @@ const mockValidationMetadatas = [
11
11
  { propertyName: "desc", name: "minLength", constraints: [5] },
12
12
  { propertyName: "desc", name: "maxLength", constraints: [100] },
13
13
  ];
14
- const mockGetMetadataStorage = jest.fn(() => ({
15
- getTargetValidationMetadatas: jest.fn(() => mockValidationMetadatas),
16
- }));
17
- jest.mock("class-validator", () => ({
18
- getMetadataStorage: mockGetMetadataStorage,
19
- }));
14
+ jest.mock("class-validator", () => {
15
+ const mockGetMetadataStorage = jest.fn(() => ({
16
+ getTargetValidationMetadatas: jest.fn(() => [
17
+ { propertyName: "name", name: "isNotEmpty", constraints: [] },
18
+ { propertyName: "age", name: "isInt", constraints: [] },
19
+ { propertyName: "email", name: "isEmail", constraints: [] },
20
+ { propertyName: "tags", name: "isOptional", constraints: [] },
21
+ { propertyName: "desc", name: "minLength", constraints: [5] },
22
+ { propertyName: "desc", name: "maxLength", constraints: [100] },
23
+ ]),
24
+ }));
25
+ return {
26
+ getMetadataStorage: mockGetMetadataStorage,
27
+ };
28
+ });
20
29
  // Helper to set Reflect metadata for property types and openapi
21
30
  function setPropertyMetadata(target, property, type, openApi) {
22
31
  Reflect.defineMetadata("design:type", type, target, property);
@@ -33,9 +42,9 @@ setPropertyMetadata(TestDto.prototype, "email", String);
33
42
  setPropertyMetadata(TestDto.prototype, "tags", Array);
34
43
  setPropertyMetadata(TestDto.prototype, "desc", String, { description: "Description", example: "A desc" });
35
44
  setPropertyMetadata(TestDto.prototype, "ignored", String, { exclude: true });
36
- describe("generateSwaggerSchema", () => {
45
+ describe("generateClassSchema", () => {
37
46
  it("should generate correct schema for class properties and validation", () => {
38
- const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
47
+ const schema = (0, swagger_schema_1.generateClassSchema)(TestDto); // ✅
39
48
  expect(schema).toEqual({
40
49
  type: "object",
41
50
  properties: {
@@ -72,7 +81,7 @@ describe("generateSwaggerSchema", () => {
72
81
  allOf: [{ type: "string" }],
73
82
  anyOf: [{ type: "string" }],
74
83
  });
75
- const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
84
+ const schema = (0, swagger_schema_1.generateClassSchema)(TestDto); // ✅
76
85
  expect(schema.properties.desc).toMatchObject({
77
86
  description: "desc field",
78
87
  example: "example",
@@ -93,13 +102,24 @@ describe("generateSwaggerSchema", () => {
93
102
  });
94
103
  it("should not include excluded properties", () => {
95
104
  setPropertyMetadata(TestDto.prototype, "ignored", String, { exclude: true });
96
- const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
105
+ const schema = (0, swagger_schema_1.generateClassSchema)(TestDto); // ✅
97
106
  expect(schema.properties.ignored).toBeUndefined();
98
107
  });
99
108
  it("should fallback to string type if type is unknown", () => {
100
109
  setPropertyMetadata(TestDto.prototype, "unknown", undefined);
101
110
  mockValidationMetadatas.push({ propertyName: "unknown", name: "isNotEmpty", constraints: [] });
102
- const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
111
+ const schema = (0, swagger_schema_1.generateClassSchema)(TestDto); // ✅
103
112
  expect(schema.properties.unknown.type).toBe("string");
104
113
  });
114
+ it("should return empty schema for null or undefined input", () => {
115
+ const schema = (0, swagger_schema_1.generateClassSchema)(null);
116
+ expect(schema).toEqual({ type: "object", properties: {}, required: [] });
117
+ });
118
+ it("should handle array controllers input gracefully", () => {
119
+ // generateSwaggerSchema (array version) should skip non-DTO classes
120
+ // generateClassSchema should never receive an array
121
+ const schema = (0, swagger_schema_1.generateClassSchema)(TestDto);
122
+ expect(schema.type).toBe("object");
123
+ expect(schema.properties).toBeDefined();
124
+ });
105
125
  });