@avleon/core 0.0.39 → 0.0.42-rc0.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 +113 -28
- package/dist/cache.test.d.ts +1 -0
- package/dist/cache.test.js +36 -0
- package/dist/controller.d.ts +2 -0
- package/dist/controller.js +13 -0
- package/dist/controller.test.d.ts +1 -0
- package/dist/controller.test.js +111 -0
- package/dist/environment-variables.test.d.ts +1 -0
- package/dist/environment-variables.test.js +70 -0
- package/dist/exceptions/http-exceptions.d.ts +1 -0
- package/dist/exceptions/http-exceptions.js +3 -1
- package/dist/file-storage.d.ts +44 -9
- package/dist/file-storage.js +209 -59
- package/dist/file-storage.test.d.ts +1 -0
- package/dist/file-storage.test.js +104 -0
- package/dist/helpers.test.d.ts +1 -0
- package/dist/helpers.test.js +95 -0
- package/dist/icore.d.ts +7 -5
- package/dist/icore.js +191 -69
- package/dist/icore.test.d.ts +1 -0
- package/dist/icore.test.js +14 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +8 -1
- package/dist/kenx-provider.test.d.ts +1 -0
- package/dist/kenx-provider.test.js +36 -0
- package/dist/logger.test.d.ts +1 -0
- package/dist/logger.test.js +42 -0
- package/dist/middleware.d.ts +3 -0
- package/dist/middleware.js +7 -0
- package/dist/middleware.test.d.ts +1 -0
- package/dist/middleware.test.js +121 -0
- package/dist/multipart.test.d.ts +1 -0
- package/dist/multipart.test.js +87 -0
- package/dist/openapi.test.d.ts +1 -0
- package/dist/openapi.test.js +111 -0
- package/dist/params.test.d.ts +1 -0
- package/dist/params.test.js +83 -0
- package/dist/queue.test.d.ts +1 -0
- package/dist/queue.test.js +79 -0
- package/dist/route-methods.test.d.ts +1 -0
- package/dist/route-methods.test.js +129 -0
- package/dist/swagger-schema.d.ts +42 -0
- package/dist/swagger-schema.js +331 -58
- package/dist/swagger-schema.test.d.ts +1 -0
- package/dist/swagger-schema.test.js +105 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.js +2 -0
- package/dist/validation.test.d.ts +1 -0
- package/dist/validation.test.js +61 -0
- package/dist/websocket.test.d.ts +1 -0
- package/dist/websocket.test.js +27 -0
- package/package.json +11 -9
package/dist/swagger-schema.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenApiProperty = OpenApiProperty;
|
|
4
|
+
exports.CreateSwaggerObjectSchema = CreateSwaggerObjectSchema;
|
|
3
5
|
exports.generateSwaggerSchema = generateSwaggerSchema;
|
|
6
|
+
exports.OpenApiResponse = OpenApiResponse;
|
|
4
7
|
/**
|
|
5
8
|
* @copyright 2024
|
|
6
9
|
* @author Tareq Hossain
|
|
@@ -8,6 +11,241 @@ exports.generateSwaggerSchema = generateSwaggerSchema;
|
|
|
8
11
|
* @url https://github.com/xtareq
|
|
9
12
|
*/
|
|
10
13
|
const class_validator_1 = require("class-validator");
|
|
14
|
+
// Decorator to add OpenAPI metadata to properties
|
|
15
|
+
function OpenApiProperty(options) {
|
|
16
|
+
return function (target, propertyKey) {
|
|
17
|
+
var _a;
|
|
18
|
+
let meta = options ? { ...options } : {};
|
|
19
|
+
if (meta.format === "binary") {
|
|
20
|
+
if (meta.isArray) {
|
|
21
|
+
meta = {
|
|
22
|
+
...meta,
|
|
23
|
+
type: "array",
|
|
24
|
+
items: (_a = meta.items) !== null && _a !== void 0 ? _a : { type: "string", format: "binary" },
|
|
25
|
+
description: meta.description || "Array of files",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
meta = {
|
|
30
|
+
...meta,
|
|
31
|
+
type: "string",
|
|
32
|
+
format: "binary",
|
|
33
|
+
description: meta.description || "File upload",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
Reflect.defineMetadata("property:openapi", meta, target, propertyKey);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function extractOpenApiFields(meta) {
|
|
41
|
+
const result = {};
|
|
42
|
+
const jsonSchemaFields = [
|
|
43
|
+
"description",
|
|
44
|
+
"deprecated",
|
|
45
|
+
"example",
|
|
46
|
+
"enum",
|
|
47
|
+
"format",
|
|
48
|
+
"default",
|
|
49
|
+
"minimum",
|
|
50
|
+
"maximum",
|
|
51
|
+
"minLength",
|
|
52
|
+
"maxLength",
|
|
53
|
+
"pattern",
|
|
54
|
+
"oneOf",
|
|
55
|
+
"allOf",
|
|
56
|
+
"anyOf",
|
|
57
|
+
];
|
|
58
|
+
// Valid JSON Schema formats
|
|
59
|
+
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",
|
|
86
|
+
];
|
|
87
|
+
jsonSchemaFields.forEach((field) => {
|
|
88
|
+
if (meta[field] !== undefined) {
|
|
89
|
+
// Validate format field
|
|
90
|
+
if (field === "format") {
|
|
91
|
+
const formatValue = meta[field];
|
|
92
|
+
// Only add format if it's a valid format string
|
|
93
|
+
if (validFormats.includes(formatValue)) {
|
|
94
|
+
result[field] = formatValue;
|
|
95
|
+
}
|
|
96
|
+
// Skip invalid formats
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
result[field] = meta[field];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
function CreateSwaggerObjectSchema(classType) {
|
|
106
|
+
const metadataStorage = (0, class_validator_1.getMetadataStorage)();
|
|
107
|
+
const validationMetadata = metadataStorage.getTargetValidationMetadatas(classType, "", true, false);
|
|
108
|
+
const schema = {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {},
|
|
111
|
+
required: [],
|
|
112
|
+
};
|
|
113
|
+
const prototype = classType.prototype;
|
|
114
|
+
const propertyKeys = new Set();
|
|
115
|
+
// Collect property names
|
|
116
|
+
Object.getOwnPropertyNames(prototype).forEach((k) => propertyKeys.add(k));
|
|
117
|
+
Object.keys(prototype).forEach((k) => propertyKeys.add(k));
|
|
118
|
+
validationMetadata.forEach((m) => propertyKeys.add(m.propertyName));
|
|
119
|
+
try {
|
|
120
|
+
const instance = new classType();
|
|
121
|
+
Reflect.ownKeys(instance).forEach((k) => {
|
|
122
|
+
if (typeof k === "string")
|
|
123
|
+
propertyKeys.add(k);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (_) { }
|
|
127
|
+
propertyKeys.forEach((propertyName) => {
|
|
128
|
+
var _a;
|
|
129
|
+
if (!propertyName || propertyName === "constructor")
|
|
130
|
+
return;
|
|
131
|
+
// Get decorator metadata
|
|
132
|
+
const openApiMeta = Reflect.getMetadata("property:openapi", prototype, propertyName);
|
|
133
|
+
if (openApiMeta === null || openApiMeta === void 0 ? void 0 : openApiMeta.exclude)
|
|
134
|
+
return;
|
|
135
|
+
// Get TypeScript type
|
|
136
|
+
const propertyType = Reflect.getMetadata("design:type", prototype, propertyName);
|
|
137
|
+
let swaggerProperty = {};
|
|
138
|
+
switch (propertyType) {
|
|
139
|
+
case String:
|
|
140
|
+
swaggerProperty.type = "string";
|
|
141
|
+
break;
|
|
142
|
+
case Number:
|
|
143
|
+
swaggerProperty.type = "number";
|
|
144
|
+
break;
|
|
145
|
+
case Boolean:
|
|
146
|
+
swaggerProperty.type = "boolean";
|
|
147
|
+
break;
|
|
148
|
+
case Date:
|
|
149
|
+
swaggerProperty.type = "string";
|
|
150
|
+
swaggerProperty.format = "date-time";
|
|
151
|
+
break;
|
|
152
|
+
case Array:
|
|
153
|
+
swaggerProperty.type = "array";
|
|
154
|
+
swaggerProperty.items = { type: "string" }; // fallback
|
|
155
|
+
break;
|
|
156
|
+
case Object:
|
|
157
|
+
swaggerProperty = CreateSwaggerObjectSchema(propertyType);
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
if (propertyType && typeof propertyType === "function") {
|
|
161
|
+
swaggerProperty.$ref = `#/components/schemas/${propertyType.name}`;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
swaggerProperty.type = ((_a = propertyType === null || propertyType === void 0 ? void 0 : propertyType.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || "string";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (openApiMeta) {
|
|
168
|
+
swaggerProperty = {
|
|
169
|
+
...swaggerProperty,
|
|
170
|
+
...openApiMeta,
|
|
171
|
+
...extractOpenApiFields(openApiMeta),
|
|
172
|
+
};
|
|
173
|
+
// 🪄 Auto-handle file uploads
|
|
174
|
+
if (openApiMeta.format === "binary") {
|
|
175
|
+
if (openApiMeta.isArray || propertyType === Array) {
|
|
176
|
+
swaggerProperty = {
|
|
177
|
+
type: "array",
|
|
178
|
+
items: { type: "string", format: "binary" },
|
|
179
|
+
description: openApiMeta.description || "Array of files",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
swaggerProperty = {
|
|
184
|
+
type: "string",
|
|
185
|
+
format: "binary",
|
|
186
|
+
description: openApiMeta.description || "File upload",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
schema.properties[propertyName] = swaggerProperty;
|
|
192
|
+
});
|
|
193
|
+
// Handle validation decorators
|
|
194
|
+
validationMetadata.forEach((meta) => {
|
|
195
|
+
const propertyName = meta.propertyName;
|
|
196
|
+
const property = schema.properties[propertyName];
|
|
197
|
+
if (!property)
|
|
198
|
+
return;
|
|
199
|
+
switch (meta.name) {
|
|
200
|
+
case "isNotEmpty":
|
|
201
|
+
case "isDefined":
|
|
202
|
+
if (!schema.required.includes(propertyName)) {
|
|
203
|
+
schema.required.push(propertyName);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case "isOptional":
|
|
207
|
+
schema.required = schema.required.filter((item) => item !== propertyName);
|
|
208
|
+
break;
|
|
209
|
+
case "minLength":
|
|
210
|
+
property.minLength = meta.constraints[0];
|
|
211
|
+
break;
|
|
212
|
+
case "maxLength":
|
|
213
|
+
property.maxLength = meta.constraints[0];
|
|
214
|
+
break;
|
|
215
|
+
case "min":
|
|
216
|
+
property.minimum = meta.constraints[0];
|
|
217
|
+
break;
|
|
218
|
+
case "max":
|
|
219
|
+
property.maximum = meta.constraints[0];
|
|
220
|
+
break;
|
|
221
|
+
case "isEmail":
|
|
222
|
+
property.format = "email";
|
|
223
|
+
break;
|
|
224
|
+
case "isDate":
|
|
225
|
+
property.format = "date-time";
|
|
226
|
+
break;
|
|
227
|
+
case "isIn":
|
|
228
|
+
property.enum = meta.constraints[0];
|
|
229
|
+
break;
|
|
230
|
+
case "isNumber":
|
|
231
|
+
property.type = "number";
|
|
232
|
+
break;
|
|
233
|
+
case "isInt":
|
|
234
|
+
property.type = "integer";
|
|
235
|
+
break;
|
|
236
|
+
case "isBoolean":
|
|
237
|
+
property.type = "boolean";
|
|
238
|
+
break;
|
|
239
|
+
case "isString":
|
|
240
|
+
property.type = "string";
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
if (schema.required.length === 0) {
|
|
245
|
+
delete schema.required;
|
|
246
|
+
}
|
|
247
|
+
return schema;
|
|
248
|
+
}
|
|
11
249
|
function generateSwaggerSchema(classType) {
|
|
12
250
|
const metadataStorage = (0, class_validator_1.getMetadataStorage)();
|
|
13
251
|
const validationMetadata = metadataStorage.getTargetValidationMetadatas(classType, "", true, false);
|
|
@@ -117,63 +355,98 @@ function generateSwaggerSchema(classType) {
|
|
|
117
355
|
});
|
|
118
356
|
return schema;
|
|
119
357
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
358
|
+
function OpenApiResponse(code = 200, model, description = "Successful response") {
|
|
359
|
+
let dataSchema;
|
|
360
|
+
if (typeof model === "function") {
|
|
361
|
+
// Class or constructor
|
|
362
|
+
dataSchema = generateSwaggerSchema(model);
|
|
363
|
+
}
|
|
364
|
+
else if (model && typeof model === "object") {
|
|
365
|
+
// Example object
|
|
366
|
+
dataSchema = inferSchemaFromExample(model);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
// Fallback
|
|
370
|
+
dataSchema = { type: "string" };
|
|
371
|
+
}
|
|
372
|
+
let message = "OK";
|
|
373
|
+
switch (code) {
|
|
374
|
+
case 400:
|
|
375
|
+
message = "Error";
|
|
376
|
+
description = "Error: Bad Request";
|
|
377
|
+
break;
|
|
378
|
+
case 401:
|
|
379
|
+
message = "Error";
|
|
380
|
+
description = "Error: Unauthorized";
|
|
381
|
+
break;
|
|
382
|
+
case 403:
|
|
383
|
+
message = "Error";
|
|
384
|
+
description = "Error: Forbidden";
|
|
385
|
+
break;
|
|
386
|
+
case 201:
|
|
387
|
+
message = "Created";
|
|
388
|
+
description = "Success: Created";
|
|
389
|
+
break;
|
|
390
|
+
case 500:
|
|
391
|
+
message = "Error";
|
|
392
|
+
description = "Error: InternalError";
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
description,
|
|
397
|
+
content: {
|
|
398
|
+
"application/json": {
|
|
399
|
+
schema: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
code: { type: "number", example: code },
|
|
403
|
+
status: { type: "string", example: message },
|
|
404
|
+
data: dataSchema,
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Infer a basic JSON schema from a plain JavaScript object.
|
|
413
|
+
*/
|
|
414
|
+
function inferSchemaFromExample(obj) {
|
|
415
|
+
var _a;
|
|
416
|
+
if (Array.isArray(obj)) {
|
|
417
|
+
return {
|
|
418
|
+
type: "array",
|
|
419
|
+
items: inferSchemaFromExample((_a = obj[0]) !== null && _a !== void 0 ? _a : {}),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (obj && typeof obj === "object") {
|
|
423
|
+
const properties = {};
|
|
424
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
425
|
+
properties[key] = inferType(value);
|
|
142
426
|
}
|
|
143
|
-
|
|
144
|
-
|
|
427
|
+
return { type: "object", properties };
|
|
428
|
+
}
|
|
429
|
+
return inferType(obj);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Infer primitive schema type
|
|
433
|
+
*/
|
|
434
|
+
function inferType(value) {
|
|
435
|
+
const type = typeof value;
|
|
436
|
+
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 };
|
|
443
|
+
case "object":
|
|
444
|
+
if (Array.isArray(value))
|
|
445
|
+
return inferSchemaFromExample(value);
|
|
446
|
+
if (value === null)
|
|
447
|
+
return { type: "null" };
|
|
448
|
+
return inferSchemaFromExample(value);
|
|
449
|
+
default:
|
|
450
|
+
return { type: "string" };
|
|
451
|
+
}
|
|
145
452
|
}
|
|
146
|
-
// export function generateSwaggerSchema(classType: any) {
|
|
147
|
-
// const { getMetadataStorage } = require("class-validator");
|
|
148
|
-
// const { plainToInstance } = require("class-transformer");
|
|
149
|
-
// const metadataStorage = getMetadataStorage();
|
|
150
|
-
// const validationMetadata = metadataStorage.getTargetValidationMetadatas(
|
|
151
|
-
// classType,
|
|
152
|
-
// "",
|
|
153
|
-
// true,
|
|
154
|
-
// );
|
|
155
|
-
// const schema: any = {
|
|
156
|
-
// type: "object",
|
|
157
|
-
// properties: {},
|
|
158
|
-
// required: [],
|
|
159
|
-
// };
|
|
160
|
-
// validationMetadata.forEach((meta: any) => {
|
|
161
|
-
// const propertyName = meta.propertyName;
|
|
162
|
-
// // Infer the type dynamically using Reflect metadata
|
|
163
|
-
// const propertyType = Reflect.getMetadata(
|
|
164
|
-
// "design:type",
|
|
165
|
-
// classType.prototype,
|
|
166
|
-
// propertyName,
|
|
167
|
-
// );
|
|
168
|
-
// schema.properties[propertyName] = {
|
|
169
|
-
// type: propertyType?.name.toLowerCase() || "string", // Default to string if type cannot be inferred
|
|
170
|
-
// };
|
|
171
|
-
// if (meta.name === "isNotEmpty") {
|
|
172
|
-
// schema.required.push(propertyName);
|
|
173
|
-
// }
|
|
174
|
-
// if (meta.name === "minLength") {
|
|
175
|
-
// schema.properties[propertyName].minLength = meta.constraints[0];
|
|
176
|
-
// }
|
|
177
|
-
// });
|
|
178
|
-
// return schema;
|
|
179
|
-
// }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("reflect-metadata");
|
|
4
|
+
const swagger_schema_1 = require("./swagger-schema");
|
|
5
|
+
// Mocks for class-validator metadata
|
|
6
|
+
const mockValidationMetadatas = [
|
|
7
|
+
{ propertyName: "name", name: "isNotEmpty", constraints: [] },
|
|
8
|
+
{ propertyName: "age", name: "isInt", constraints: [] },
|
|
9
|
+
{ propertyName: "email", name: "isEmail", constraints: [] },
|
|
10
|
+
{ propertyName: "tags", name: "isOptional", constraints: [] },
|
|
11
|
+
{ propertyName: "desc", name: "minLength", constraints: [5] },
|
|
12
|
+
{ propertyName: "desc", name: "maxLength", constraints: [100] },
|
|
13
|
+
];
|
|
14
|
+
const mockGetMetadataStorage = jest.fn(() => ({
|
|
15
|
+
getTargetValidationMetadatas: jest.fn(() => mockValidationMetadatas),
|
|
16
|
+
}));
|
|
17
|
+
jest.mock("class-validator", () => ({
|
|
18
|
+
getMetadataStorage: mockGetMetadataStorage,
|
|
19
|
+
}));
|
|
20
|
+
// Helper to set Reflect metadata for property types and openapi
|
|
21
|
+
function setPropertyMetadata(target, property, type, openApi) {
|
|
22
|
+
Reflect.defineMetadata("design:type", type, target, property);
|
|
23
|
+
if (openApi) {
|
|
24
|
+
Reflect.defineMetadata("property:openapi", openApi, target, property);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Test class
|
|
28
|
+
class TestDto {
|
|
29
|
+
}
|
|
30
|
+
setPropertyMetadata(TestDto.prototype, "name", String);
|
|
31
|
+
setPropertyMetadata(TestDto.prototype, "age", Number);
|
|
32
|
+
setPropertyMetadata(TestDto.prototype, "email", String);
|
|
33
|
+
setPropertyMetadata(TestDto.prototype, "tags", Array);
|
|
34
|
+
setPropertyMetadata(TestDto.prototype, "desc", String, { description: "Description", example: "A desc" });
|
|
35
|
+
setPropertyMetadata(TestDto.prototype, "ignored", String, { exclude: true });
|
|
36
|
+
describe("generateSwaggerSchema", () => {
|
|
37
|
+
it("should generate correct schema for class properties and validation", () => {
|
|
38
|
+
const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
|
|
39
|
+
expect(schema).toEqual({
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
name: { type: "string" },
|
|
43
|
+
age: { type: "integer" },
|
|
44
|
+
email: { type: "string", format: "email" },
|
|
45
|
+
tags: { type: "array", items: { type: "string" } },
|
|
46
|
+
desc: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Description",
|
|
49
|
+
example: "A desc",
|
|
50
|
+
minLength: 5,
|
|
51
|
+
maxLength: 100,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ["name", "age", "email", "desc"],
|
|
55
|
+
});
|
|
56
|
+
expect(schema.properties.ignored).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
it("should handle openapi metadata fields", () => {
|
|
59
|
+
setPropertyMetadata(TestDto.prototype, "desc", String, {
|
|
60
|
+
description: "desc field",
|
|
61
|
+
example: "example",
|
|
62
|
+
deprecated: true,
|
|
63
|
+
enum: ["a", "b"],
|
|
64
|
+
format: "custom-format",
|
|
65
|
+
default: "default",
|
|
66
|
+
minimum: 1,
|
|
67
|
+
maximum: 10,
|
|
68
|
+
minLength: 2,
|
|
69
|
+
maxLength: 20,
|
|
70
|
+
pattern: ".*",
|
|
71
|
+
oneOf: [{ type: "string" }],
|
|
72
|
+
allOf: [{ type: "string" }],
|
|
73
|
+
anyOf: [{ type: "string" }],
|
|
74
|
+
});
|
|
75
|
+
const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
|
|
76
|
+
expect(schema.properties.desc).toMatchObject({
|
|
77
|
+
description: "desc field",
|
|
78
|
+
example: "example",
|
|
79
|
+
deprecated: true,
|
|
80
|
+
enum: ["a", "b"],
|
|
81
|
+
format: "custom-format",
|
|
82
|
+
default: "default",
|
|
83
|
+
minimum: 1,
|
|
84
|
+
maximum: 10,
|
|
85
|
+
minLength: 2,
|
|
86
|
+
maxLength: 20,
|
|
87
|
+
pattern: ".*",
|
|
88
|
+
oneOf: [{ type: "string" }],
|
|
89
|
+
allOf: [{ type: "string" }],
|
|
90
|
+
anyOf: [{ type: "string" }],
|
|
91
|
+
type: "string",
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
it("should not include excluded properties", () => {
|
|
95
|
+
setPropertyMetadata(TestDto.prototype, "ignored", String, { exclude: true });
|
|
96
|
+
const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
|
|
97
|
+
expect(schema.properties.ignored).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
it("should fallback to string type if type is unknown", () => {
|
|
100
|
+
setPropertyMetadata(TestDto.prototype, "unknown", undefined);
|
|
101
|
+
mockValidationMetadatas.push({ propertyName: "unknown", name: "isNotEmpty", constraints: [] });
|
|
102
|
+
const schema = (0, swagger_schema_1.generateSwaggerSchema)(TestDto);
|
|
103
|
+
expect(schema.properties.unknown.type).toBe("string");
|
|
104
|
+
});
|
|
105
|
+
});
|
package/dist/validation.d.ts
CHANGED
|
@@ -28,5 +28,12 @@ export type ValidationProps = {
|
|
|
28
28
|
export type ValidateOptons = {
|
|
29
29
|
location?: "header" | "queryparam" | "param" | "body" | "custom";
|
|
30
30
|
};
|
|
31
|
+
export declare class Validator {
|
|
32
|
+
private rules;
|
|
33
|
+
private options;
|
|
34
|
+
constructor(obj: ValidationProps, options?: ValidateOptons);
|
|
35
|
+
private init;
|
|
36
|
+
validate(obj: any | Array<any>, options?: ValidateOptons): any[];
|
|
37
|
+
}
|
|
31
38
|
export declare function validateOrThrow<T extends {}>(obj: T, rules: ValidationProps, options?: ValidateOptons): any;
|
|
32
39
|
export {};
|
package/dist/validation.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @url https://github.com/xtareq
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Validator = void 0;
|
|
9
10
|
exports.validateOrThrow = validateOrThrow;
|
|
10
11
|
const exceptions_1 = require("./exceptions");
|
|
11
12
|
class PValidationRule {
|
|
@@ -76,6 +77,7 @@ class Validator {
|
|
|
76
77
|
return [erors, obj];
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
exports.Validator = Validator;
|
|
79
81
|
const isBool = (val) => {
|
|
80
82
|
if (typeof val == "boolean")
|
|
81
83
|
return true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const validation_1 = require("./validation");
|
|
4
|
+
describe("Validator.validate", () => {
|
|
5
|
+
const rules = {
|
|
6
|
+
username: { type: "string", required: true },
|
|
7
|
+
age: { type: "number", required: true },
|
|
8
|
+
isActive: { type: "boolean", required: true },
|
|
9
|
+
};
|
|
10
|
+
it("should pass validation for correct types", () => {
|
|
11
|
+
const validator = new validation_1.Validator(rules);
|
|
12
|
+
const input = { username: "john", age: 25, isActive: true };
|
|
13
|
+
const [errors, validated] = validator.validate(input);
|
|
14
|
+
expect(errors).toEqual([]);
|
|
15
|
+
expect(validated).toEqual({ username: "john", age: 25, isActive: true });
|
|
16
|
+
});
|
|
17
|
+
it("should fail when required fields are missing", () => {
|
|
18
|
+
const validator = new validation_1.Validator(rules);
|
|
19
|
+
const input = { age: 30 };
|
|
20
|
+
const [errors] = validator.validate(input);
|
|
21
|
+
expect(errors).toEqual(expect.arrayContaining([
|
|
22
|
+
expect.objectContaining({ path: "username" }),
|
|
23
|
+
expect.objectContaining({ path: "isActive" }),
|
|
24
|
+
]));
|
|
25
|
+
});
|
|
26
|
+
it("should fail when types are incorrect", () => {
|
|
27
|
+
const validator = new validation_1.Validator(rules);
|
|
28
|
+
const input = { username: 123, age: "notanumber", isActive: "notabool" };
|
|
29
|
+
const [errors] = validator.validate(input);
|
|
30
|
+
expect(errors).toEqual(expect.arrayContaining([
|
|
31
|
+
expect.objectContaining({ path: "username" }),
|
|
32
|
+
expect.objectContaining({ path: "age" }),
|
|
33
|
+
expect.objectContaining({ path: "isActive" }),
|
|
34
|
+
]));
|
|
35
|
+
});
|
|
36
|
+
it("should coerce number and boolean types", () => {
|
|
37
|
+
const validator = new validation_1.Validator(rules);
|
|
38
|
+
const input = { username: "john", age: "42", isActive: "true" };
|
|
39
|
+
const [errors, validated] = validator.validate(input);
|
|
40
|
+
expect(errors).toEqual([]);
|
|
41
|
+
expect(validated.age).toBe(42);
|
|
42
|
+
expect(validated.isActive).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
it("should handle boolean values as 0/1 and 'true'/'false'", () => {
|
|
45
|
+
const validator = new validation_1.Validator(rules);
|
|
46
|
+
const input1 = { username: "john", age: 20, isActive: 1 };
|
|
47
|
+
const input2 = { username: "john", age: 20, isActive: "false" };
|
|
48
|
+
const [errors1, validated1] = validator.validate(input1);
|
|
49
|
+
const [errors2, validated2] = validator.validate(input2);
|
|
50
|
+
expect(errors1).toEqual([]);
|
|
51
|
+
expect(validated1.isActive).toBe(true);
|
|
52
|
+
expect(errors2).toEqual([]);
|
|
53
|
+
expect(validated2.isActive).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it("should include location in error if option is set", () => {
|
|
56
|
+
const validator = new validation_1.Validator(rules, { location: "body" });
|
|
57
|
+
const input = { age: 30 };
|
|
58
|
+
const [errors] = validator.validate(input);
|
|
59
|
+
expect(errors[0]).toHaveProperty("location", "body");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const websocket_1 = require("./websocket");
|
|
4
|
+
describe("AvleonSocketIo", () => {
|
|
5
|
+
let avleonSocketIo;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
avleonSocketIo = new websocket_1.AvleonSocketIo();
|
|
8
|
+
});
|
|
9
|
+
it("should be defined", () => {
|
|
10
|
+
expect(avleonSocketIo).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
it("should have sendToAll method", () => {
|
|
13
|
+
expect(typeof avleonSocketIo.sendToAll).toBe("function");
|
|
14
|
+
});
|
|
15
|
+
it("should have sendOnly method", () => {
|
|
16
|
+
expect(typeof avleonSocketIo.sendOnly).toBe("function");
|
|
17
|
+
});
|
|
18
|
+
it("should have sendRoom method", () => {
|
|
19
|
+
expect(typeof avleonSocketIo.sendRoom).toBe("function");
|
|
20
|
+
});
|
|
21
|
+
it("should have receive method", () => {
|
|
22
|
+
expect(typeof avleonSocketIo.receive).toBe("function");
|
|
23
|
+
});
|
|
24
|
+
it("receive should accept a channel string", () => {
|
|
25
|
+
expect(() => avleonSocketIo.receive("test-channel")).not.toThrow();
|
|
26
|
+
});
|
|
27
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42-rc0.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"keywords": [
|
|
@@ -25,22 +25,24 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"class-transformer": "^0.5.1",
|
|
27
27
|
"class-validator": "^0.14.2",
|
|
28
|
-
"fastify": "^5.
|
|
28
|
+
"fastify": "^5.1.0",
|
|
29
|
+
"mime": "^4.1.0",
|
|
29
30
|
"pino": "^9.10.0",
|
|
31
|
+
"pino-pretty": "^13.1.1",
|
|
30
32
|
"reflect-metadata": "^0.2.2",
|
|
31
33
|
"typedi": "^0.10.0"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|
|
34
|
-
"@fastify/cors": "
|
|
35
|
-
"@fastify/multipart": "
|
|
36
|
-
"@fastify/static": "
|
|
37
|
-
"@fastify/swagger": "
|
|
38
|
-
"@fastify/swagger-ui": "
|
|
39
|
-
"@fastify/view": "
|
|
36
|
+
"@fastify/cors": "^11.0.0",
|
|
37
|
+
"@fastify/multipart": "^9.0.3",
|
|
38
|
+
"@fastify/static": "^8.1.1",
|
|
39
|
+
"@fastify/swagger": "^9.4.0",
|
|
40
|
+
"@fastify/swagger-ui": "^5.1.0",
|
|
41
|
+
"@fastify/view": "^11.0.0",
|
|
40
42
|
"@scalar/fastify-api-reference": "*",
|
|
41
43
|
"bcryptjs": "3.0.2",
|
|
42
44
|
"dotenv": "*",
|
|
43
|
-
"fastify-socket.io": "
|
|
45
|
+
"fastify-socket.io": "^4.0.0",
|
|
44
46
|
"highlight.js": "*",
|
|
45
47
|
"ioredis": "*",
|
|
46
48
|
"knex": "*",
|