@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/icore.js
CHANGED
|
@@ -62,6 +62,8 @@ const socket_io_1 = require("socket.io");
|
|
|
62
62
|
const event_subscriber_1 = require("./event-subscriber");
|
|
63
63
|
const stream_1 = __importDefault(require("stream"));
|
|
64
64
|
const kenx_provider_1 = require("./kenx-provider");
|
|
65
|
+
const controller_1 = require("./controller");
|
|
66
|
+
const mime_1 = __importDefault(require("mime"));
|
|
65
67
|
const isTsNode = process.env.TS_NODE_DEV ||
|
|
66
68
|
process.env.TS_NODE_PROJECT ||
|
|
67
69
|
process[Symbol.for("ts-node.register.instance")];
|
|
@@ -112,6 +114,17 @@ class AvleonApplication {
|
|
|
112
114
|
}
|
|
113
115
|
return AvleonApplication.instance;
|
|
114
116
|
}
|
|
117
|
+
useLogger(corsOptions) {
|
|
118
|
+
let coptions = {};
|
|
119
|
+
if (corsOptions) {
|
|
120
|
+
if (this._isConfigClass(corsOptions)) {
|
|
121
|
+
coptions = this.appConfig.get(corsOptions);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
coptions = corsOptions;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
115
128
|
isDevelopment() {
|
|
116
129
|
const env = container_1.default.get(environment_variables_1.Environment);
|
|
117
130
|
return env.get("NODE_ENV") == "development";
|
|
@@ -128,7 +141,7 @@ class AvleonApplication {
|
|
|
128
141
|
if (options.ui && options.ui == "scalar") {
|
|
129
142
|
const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
|
|
130
143
|
failOnMissing: true,
|
|
131
|
-
customMessage:
|
|
144
|
+
customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
|
|
132
145
|
});
|
|
133
146
|
await this.app.register(scalarPlugin, {
|
|
134
147
|
routePrefix: rPrefix,
|
|
@@ -147,7 +160,7 @@ class AvleonApplication {
|
|
|
147
160
|
else {
|
|
148
161
|
const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
|
|
149
162
|
failOnMissing: true,
|
|
150
|
-
customMessage:
|
|
163
|
+
customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
|
|
151
164
|
});
|
|
152
165
|
await this.app.register(fastifySwaggerUi, {
|
|
153
166
|
logo: logo ? logo : null,
|
|
@@ -181,7 +194,7 @@ class AvleonApplication {
|
|
|
181
194
|
async _initWebSocket(options) {
|
|
182
195
|
const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
|
|
183
196
|
failOnMissing: true,
|
|
184
|
-
customMessage:
|
|
197
|
+
customMessage: 'Install "fastify-socket.io" to enable socket.io.\n\n run pnpm install fastify-socket.io',
|
|
185
198
|
});
|
|
186
199
|
await this.app.register(fsSocketIO, options);
|
|
187
200
|
const socketIO = await this.app.io;
|
|
@@ -209,8 +222,8 @@ class AvleonApplication {
|
|
|
209
222
|
if (multipartOptions) {
|
|
210
223
|
this.multipartOptions = multipartOptions;
|
|
211
224
|
this.app.register(multipart_1.default, {
|
|
212
|
-
...this.multipartOptions,
|
|
213
225
|
attachFieldsToBody: true,
|
|
226
|
+
...this.multipartOptions,
|
|
214
227
|
});
|
|
215
228
|
}
|
|
216
229
|
}
|
|
@@ -281,6 +294,7 @@ class AvleonApplication {
|
|
|
281
294
|
* @returns void
|
|
282
295
|
*/
|
|
283
296
|
async buildController(controller) {
|
|
297
|
+
var _a, _b;
|
|
284
298
|
const ctrl = typedi_1.default.get(controller);
|
|
285
299
|
const controllerMeta = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, ctrl.constructor);
|
|
286
300
|
if (!controllerMeta)
|
|
@@ -318,10 +332,39 @@ class AvleonApplication {
|
|
|
318
332
|
if (!swaggerMeta.body && bodySchema) {
|
|
319
333
|
schema = { ...schema, body: bodySchema };
|
|
320
334
|
}
|
|
335
|
+
// const isMultipart =
|
|
336
|
+
// schema.consumes?.includes('multipart/form-data') ||
|
|
337
|
+
// (schema.body && schema.body.type === 'object' &&
|
|
338
|
+
// Object.values(schema.body.properties || {}).some((p: any) => p.format === 'binary'));
|
|
339
|
+
const isMultipart = ((_a = schema === null || schema === void 0 ? void 0 : schema.consumes) === null || _a === void 0 ? void 0 : _a.includes("multipart/form-data")) ||
|
|
340
|
+
Object.values(((_b = schema === null || schema === void 0 ? void 0 : schema.body) === null || _b === void 0 ? void 0 : _b.properties) || {}).some((p) => p.format === "binary");
|
|
341
|
+
// Prepare the route schema
|
|
342
|
+
let routeSchema = schema;
|
|
343
|
+
if (isMultipart) {
|
|
344
|
+
schema.consumes = ["multipart/form-data"];
|
|
345
|
+
if (!schema.body) {
|
|
346
|
+
schema.body = {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
for (const param of allMeta.body) {
|
|
352
|
+
if (param.type == "route:file") {
|
|
353
|
+
schema.body.properties[param.key] = {
|
|
354
|
+
type: "string",
|
|
355
|
+
format: "binary",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
schema.body.properties[param.key] = { type: param.dataType };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
321
363
|
this.app.route({
|
|
322
364
|
url: routePath,
|
|
323
365
|
method: methodmetaOptions.method.toUpperCase(),
|
|
324
|
-
schema:
|
|
366
|
+
schema: routeSchema,
|
|
367
|
+
attachValidation: isMultipart,
|
|
325
368
|
handler: async (req, res) => {
|
|
326
369
|
let reqClone = req;
|
|
327
370
|
// class level authrization
|
|
@@ -368,23 +411,25 @@ class AvleonApplication {
|
|
|
368
411
|
(0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: "queryparam" });
|
|
369
412
|
}
|
|
370
413
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
code
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
414
|
+
if (!isMultipart) {
|
|
415
|
+
for (let bodyMeta of allMeta.body) {
|
|
416
|
+
if (bodyMeta.validatorClass) {
|
|
417
|
+
const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
|
|
418
|
+
if (err) {
|
|
419
|
+
return await res.code(400).send({
|
|
420
|
+
code: 400,
|
|
421
|
+
error: "ValidationError",
|
|
422
|
+
errors: err,
|
|
423
|
+
message: err.message,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
381
426
|
}
|
|
382
427
|
}
|
|
383
428
|
}
|
|
384
429
|
const result = await prototype[method].apply(ctrl, args);
|
|
385
430
|
// Custom wrapped file download
|
|
386
|
-
if (result === null || result === void 0 ? void 0 : result.
|
|
387
|
-
const { stream, filename
|
|
431
|
+
if (result === null || result === void 0 ? void 0 : result.download) {
|
|
432
|
+
const { stream, filename } = result;
|
|
388
433
|
if (!stream || typeof stream.pipe !== "function") {
|
|
389
434
|
return res.code(500).send({
|
|
390
435
|
code: 500,
|
|
@@ -392,6 +437,9 @@ class AvleonApplication {
|
|
|
392
437
|
message: "Invalid stream object",
|
|
393
438
|
});
|
|
394
439
|
}
|
|
440
|
+
const contentType = result.contentType ||
|
|
441
|
+
mime_1.default.getType(filename) ||
|
|
442
|
+
"application/octet-stream";
|
|
395
443
|
res.header("Content-Type", contentType);
|
|
396
444
|
res.header("Content-Disposition", `attachment; filename="${filename}"`);
|
|
397
445
|
stream.on("error", (err) => {
|
|
@@ -427,61 +475,128 @@ class AvleonApplication {
|
|
|
427
475
|
}
|
|
428
476
|
}
|
|
429
477
|
/**
|
|
430
|
-
*
|
|
431
|
-
* @param req
|
|
432
|
-
* @param meta
|
|
433
|
-
* @returns
|
|
478
|
+
* Maps request data to controller method arguments based on decorators
|
|
479
|
+
* @param req - The incoming request object
|
|
480
|
+
* @param meta - Metadata about method parameters
|
|
481
|
+
* @returns Array of arguments to pass to the controller method
|
|
434
482
|
*/
|
|
435
483
|
async _mapArgs(req, meta) {
|
|
436
|
-
var _a;
|
|
484
|
+
var _a, _b, _c, _d;
|
|
485
|
+
// Initialize args cache on request if not present
|
|
437
486
|
if (!req.hasOwnProperty("_argsCache")) {
|
|
438
487
|
Object.defineProperty(req, "_argsCache", {
|
|
439
488
|
value: new Map(),
|
|
440
489
|
enumerable: false,
|
|
490
|
+
writable: false,
|
|
491
|
+
configurable: false,
|
|
441
492
|
});
|
|
442
493
|
}
|
|
443
494
|
const cache = req._argsCache;
|
|
444
|
-
const cacheKey = JSON.stringify(meta);
|
|
495
|
+
const cacheKey = JSON.stringify(meta);
|
|
496
|
+
// Return cached result if available
|
|
445
497
|
if (cache.has(cacheKey)) {
|
|
446
498
|
return cache.get(cacheKey);
|
|
447
499
|
}
|
|
448
|
-
|
|
449
|
-
meta.query.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
meta.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
500
|
+
// Initialize args array with correct length
|
|
501
|
+
const maxIndex = Math.max(...meta.params.map((p) => p.index || 0), ...meta.query.map((q) => q.index), ...meta.body.map((b) => b.index), ...meta.currentUser.map((u) => u.index), ...meta.headers.map((h) => h.index), ...(((_a = meta.request) === null || _a === void 0 ? void 0 : _a.map((r) => r.index)) || []), ...(((_b = meta.file) === null || _b === void 0 ? void 0 : _b.map((f) => f.index)) || []), ...(((_c = meta.files) === null || _c === void 0 ? void 0 : _c.map((f) => f.index)) || []), -1) + 1;
|
|
502
|
+
const args = new Array(maxIndex).fill(undefined);
|
|
503
|
+
// Map route parameters
|
|
504
|
+
meta.params.forEach((p) => {
|
|
505
|
+
args[p.index] =
|
|
506
|
+
p.key == "all" ? { ...req.query } : req.params[p.key] || null;
|
|
507
|
+
});
|
|
508
|
+
// Map query parameters
|
|
509
|
+
meta.query.forEach((q) => {
|
|
510
|
+
args[q.index] = q.key == "all" ? { ...req.query } : req.query[q.key];
|
|
511
|
+
});
|
|
512
|
+
// Map body data (including form data)
|
|
513
|
+
meta.body.forEach((body) => {
|
|
514
|
+
args[body.index] = { ...req.body, ...req.formData };
|
|
515
|
+
});
|
|
516
|
+
// Map current user
|
|
517
|
+
meta.currentUser.forEach((user) => {
|
|
518
|
+
args[user.index] = req.user;
|
|
519
|
+
});
|
|
520
|
+
// Map headers
|
|
521
|
+
meta.headers.forEach((header) => {
|
|
522
|
+
args[header.index] =
|
|
523
|
+
header.key === "all" ? { ...req.headers } : req.headers[header.key];
|
|
524
|
+
});
|
|
525
|
+
// Map request object
|
|
526
|
+
if (meta.request && meta.request.length > 0) {
|
|
527
|
+
meta.request.forEach((r) => {
|
|
528
|
+
args[r.index] = req;
|
|
529
|
+
});
|
|
458
530
|
}
|
|
459
|
-
|
|
460
|
-
|
|
531
|
+
// Handle file uploads (single or multiple files)
|
|
532
|
+
const needsFiles = (meta.file && meta.file.length > 0) ||
|
|
533
|
+
(meta.files && meta.files.length > 0);
|
|
534
|
+
if (needsFiles &&
|
|
535
|
+
((_d = req.headers["content-type"]) === null || _d === void 0 ? void 0 : _d.startsWith("multipart/form-data"))) {
|
|
461
536
|
const files = await req.saveRequestFiles();
|
|
462
537
|
if (!files || files.length === 0) {
|
|
463
|
-
|
|
538
|
+
// Only throw error if files are explicitly required
|
|
539
|
+
if (meta.files && meta.files.length > 0) {
|
|
540
|
+
throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
|
|
541
|
+
}
|
|
542
|
+
// For single file (@File()), set to null
|
|
543
|
+
if (meta.file && meta.file.length > 0) {
|
|
544
|
+
meta.file.forEach((f) => {
|
|
545
|
+
args[f.index] = null;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
464
548
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
549
|
+
else {
|
|
550
|
+
// Create file info objects
|
|
551
|
+
const fileInfo = files.map((file) => ({
|
|
552
|
+
type: file.type,
|
|
553
|
+
filepath: file.filepath,
|
|
554
|
+
fieldname: file.fieldname,
|
|
555
|
+
filename: file.filename,
|
|
556
|
+
encoding: file.encoding,
|
|
557
|
+
mimetype: file.mimetype,
|
|
558
|
+
fields: file.fields,
|
|
559
|
+
}));
|
|
560
|
+
// Handle single file decorator (@File())
|
|
561
|
+
if (meta.file && meta.file.length > 0) {
|
|
562
|
+
meta.file.forEach((f) => {
|
|
563
|
+
if (f.fieldName === "all") {
|
|
564
|
+
// Return first file if "all" is specified
|
|
565
|
+
args[f.index] = fileInfo[0] || null;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
// Find specific file by fieldname
|
|
569
|
+
const file = fileInfo.find((x) => x.fieldname === f.fieldName);
|
|
570
|
+
if (!file) {
|
|
571
|
+
throw new exceptions_1.BadRequestException(`File field "${f.fieldName}" not found in uploaded files`);
|
|
572
|
+
}
|
|
573
|
+
args[f.index] = file;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
if (meta.files && meta.files.length > 0) {
|
|
578
|
+
meta.files.forEach((f) => {
|
|
579
|
+
if (f.fieldName === "all") {
|
|
580
|
+
args[f.index] = fileInfo;
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const matchingFiles = fileInfo.filter((x) => x.fieldname === f.fieldName);
|
|
584
|
+
if (matchingFiles.length === 0) {
|
|
585
|
+
throw new exceptions_1.BadRequestException(`No files found for field "${f.fieldName}"`);
|
|
586
|
+
}
|
|
587
|
+
args[f.index] = matchingFiles;
|
|
588
|
+
}
|
|
589
|
+
});
|
|
478
590
|
}
|
|
479
|
-
args[f.index] =
|
|
480
|
-
f.fieldName == "all"
|
|
481
|
-
? fileInfo
|
|
482
|
-
: fileInfo.filter((x) => x.fieldname == f.fieldName);
|
|
483
591
|
}
|
|
484
592
|
}
|
|
593
|
+
else if (needsFiles) {
|
|
594
|
+
// Files expected but request is not multipart/form-data
|
|
595
|
+
throw new exceptions_1.BadRequestException({
|
|
596
|
+
error: "Invalid content type. Expected multipart/form-data for file uploads",
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
// Cache the result
|
|
485
600
|
cache.set(cacheKey, args);
|
|
486
601
|
return args;
|
|
487
602
|
}
|
|
@@ -497,6 +612,7 @@ class AvleonApplication {
|
|
|
497
612
|
return this.metaCache.get(cacheKey);
|
|
498
613
|
}
|
|
499
614
|
const meta = {
|
|
615
|
+
request: Reflect.getMetadata(controller_1.REQUEST_METADATA_KEY, prototype, method) || [],
|
|
500
616
|
params: Reflect.getMetadata(container_1.PARAM_META_KEY, prototype, method) || [],
|
|
501
617
|
query: Reflect.getMetadata(container_1.QUERY_META_KEY, prototype, method) || [],
|
|
502
618
|
body: Reflect.getMetadata(container_1.REQUEST_BODY_META_KEY, prototype, method) || [],
|
|
@@ -557,7 +673,7 @@ class AvleonApplication {
|
|
|
557
673
|
if (this.controllers.length > 0) {
|
|
558
674
|
for (let controller of this.controllers) {
|
|
559
675
|
if ((0, container_1.isApiController)(controller)) {
|
|
560
|
-
this.buildController(controller);
|
|
676
|
+
await this.buildController(controller);
|
|
561
677
|
}
|
|
562
678
|
else {
|
|
563
679
|
throw new system_exception_1.SystemUseError("Not a api controller.");
|
|
@@ -677,7 +793,7 @@ class AvleonApplication {
|
|
|
677
793
|
this.rMap.forEach((value, key) => {
|
|
678
794
|
const [m, r] = key.split(":");
|
|
679
795
|
this.app.route({
|
|
680
|
-
method: m,
|
|
796
|
+
method: m.toUpperCase(),
|
|
681
797
|
url: r,
|
|
682
798
|
schema: value.schema || {},
|
|
683
799
|
preHandler: value.middlewares ? value.middlewares : [],
|
|
@@ -687,19 +803,6 @@ class AvleonApplication {
|
|
|
687
803
|
},
|
|
688
804
|
});
|
|
689
805
|
});
|
|
690
|
-
this.app.setErrorHandler(async (error, req, res) => {
|
|
691
|
-
const handledErr = this._handleError(error);
|
|
692
|
-
if (error instanceof exceptions_1.ValidationErrorException ||
|
|
693
|
-
error instanceof exceptions_1.BadRequestException) {
|
|
694
|
-
return res.status(handledErr.code).send({
|
|
695
|
-
code: handledErr.code,
|
|
696
|
-
error: handledErr.error,
|
|
697
|
-
errors: handledErr.message,
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
return res.status(handledErr.code).send(handledErr);
|
|
701
|
-
});
|
|
702
|
-
await this.app.ready();
|
|
703
806
|
if (this._hasWebsocket) {
|
|
704
807
|
await this.app.io.on("connection", this.handleSocket);
|
|
705
808
|
await this.app.io.use((socket, next) => {
|
|
@@ -714,6 +817,25 @@ class AvleonApplication {
|
|
|
714
817
|
}
|
|
715
818
|
});
|
|
716
819
|
}
|
|
820
|
+
this.app.setErrorHandler((error, request, reply) => {
|
|
821
|
+
if (error instanceof exceptions_1.BaseHttpException) {
|
|
822
|
+
const response = {
|
|
823
|
+
code: error.code,
|
|
824
|
+
status: "Error",
|
|
825
|
+
data: error.payload,
|
|
826
|
+
};
|
|
827
|
+
return reply
|
|
828
|
+
.status(error.code || 500)
|
|
829
|
+
.type("application/json")
|
|
830
|
+
.serializer((payload) => JSON.stringify(payload))
|
|
831
|
+
.send(response);
|
|
832
|
+
}
|
|
833
|
+
return reply.status(500).send({
|
|
834
|
+
code: 500,
|
|
835
|
+
message: error.message || "Internal Server Error",
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
await this.app.ready();
|
|
717
839
|
await this.app.listen({ port });
|
|
718
840
|
console.log(`Application running on http://127.0.0.1:${port}`);
|
|
719
841
|
}
|
|
@@ -723,7 +845,7 @@ class AvleonApplication {
|
|
|
723
845
|
this.rMap.forEach((value, key) => {
|
|
724
846
|
const [m, r] = key.split(":");
|
|
725
847
|
this.app.route({
|
|
726
|
-
method: m,
|
|
848
|
+
method: m.toUpperCase(),
|
|
727
849
|
url: r,
|
|
728
850
|
schema: value.schema || {},
|
|
729
851
|
preHandler: value.middlewares ? value.middlewares : [],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const icore_1 = require("./icore");
|
|
4
|
+
describe("Avleon.createApplication", () => {
|
|
5
|
+
it("should return an instance of AvleonApplication", () => {
|
|
6
|
+
const app = icore_1.Avleon.createApplication();
|
|
7
|
+
expect(app).toBeInstanceOf(icore_1.AvleonApplication);
|
|
8
|
+
});
|
|
9
|
+
it("should always return the same instance (singleton)", () => {
|
|
10
|
+
const app1 = icore_1.Avleon.createApplication();
|
|
11
|
+
const app2 = icore_1.Avleon.createApplication();
|
|
12
|
+
expect(app1).toBe(app2);
|
|
13
|
+
});
|
|
14
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -28,4 +28,28 @@ export * from "./event-dispatcher";
|
|
|
28
28
|
export * from "./kenx-provider";
|
|
29
29
|
export { Subscribe, Private } from "./event-subscriber";
|
|
30
30
|
export declare const GetSchema: typeof sw.generateSwaggerSchema;
|
|
31
|
+
export declare const GetObjectSchema: typeof sw.CreateSwaggerObjectSchema;
|
|
32
|
+
export declare const OpenApiOk: (args1: any) => {
|
|
33
|
+
description: string;
|
|
34
|
+
content: {
|
|
35
|
+
"application/json": {
|
|
36
|
+
schema: {
|
|
37
|
+
type: string;
|
|
38
|
+
properties: {
|
|
39
|
+
code: {
|
|
40
|
+
type: string;
|
|
41
|
+
example: number;
|
|
42
|
+
};
|
|
43
|
+
status: {
|
|
44
|
+
type: string;
|
|
45
|
+
example: string;
|
|
46
|
+
};
|
|
47
|
+
data: any;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export declare const OpenApiResponse: typeof sw.OpenApiResponse;
|
|
54
|
+
export declare const OpenApiProperty: typeof sw.OpenApiProperty;
|
|
31
55
|
export { default as AvleonContainer } from "./container";
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.AvleonContainer = exports.GetSchema = exports.Private = exports.Subscribe = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
|
|
42
|
+
exports.AvleonContainer = exports.OpenApiProperty = exports.OpenApiResponse = exports.OpenApiOk = exports.GetObjectSchema = exports.GetSchema = exports.Private = exports.Subscribe = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
|
|
43
43
|
/**
|
|
44
44
|
* @copyright 2024
|
|
45
45
|
* @author Tareq Hossain
|
|
@@ -76,5 +76,12 @@ var event_subscriber_1 = require("./event-subscriber");
|
|
|
76
76
|
Object.defineProperty(exports, "Subscribe", { enumerable: true, get: function () { return event_subscriber_1.Subscribe; } });
|
|
77
77
|
Object.defineProperty(exports, "Private", { enumerable: true, get: function () { return event_subscriber_1.Private; } });
|
|
78
78
|
exports.GetSchema = sw.generateSwaggerSchema;
|
|
79
|
+
exports.GetObjectSchema = sw.CreateSwaggerObjectSchema;
|
|
80
|
+
const OpenApiOk = (args1) => {
|
|
81
|
+
return sw.OpenApiResponse(200, args1, "Success");
|
|
82
|
+
};
|
|
83
|
+
exports.OpenApiOk = OpenApiOk;
|
|
84
|
+
exports.OpenApiResponse = sw.OpenApiResponse;
|
|
85
|
+
exports.OpenApiProperty = sw.OpenApiProperty;
|
|
79
86
|
var container_1 = require("./container");
|
|
80
87
|
Object.defineProperty(exports, "AvleonContainer", { enumerable: true, get: function () { return __importDefault(container_1).default; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("reflect-metadata");
|
|
4
|
+
const typedi_1 = require("typedi");
|
|
5
|
+
const kenx_provider_1 = require("./kenx-provider");
|
|
6
|
+
jest.mock("knex", () => jest.fn(() => ({ mock: "knexInstance" })));
|
|
7
|
+
describe("DB", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
typedi_1.Container.reset();
|
|
10
|
+
});
|
|
11
|
+
it("should throw error if client is accessed before init", () => {
|
|
12
|
+
const db = new kenx_provider_1.DB();
|
|
13
|
+
expect(() => db.client).toThrow("Knex is not initialized. Call DB.init(config) first.");
|
|
14
|
+
});
|
|
15
|
+
it("should initialize knex and set connection", () => {
|
|
16
|
+
const db = new kenx_provider_1.DB();
|
|
17
|
+
const config = { client: "sqlite3", connection: {} };
|
|
18
|
+
const conn = db.init(config);
|
|
19
|
+
expect(conn).toEqual({ mock: "knexInstance" });
|
|
20
|
+
expect(db.client).toEqual({ mock: "knexInstance" });
|
|
21
|
+
expect(typedi_1.Container.get("KnexConnection")).toEqual({ mock: "knexInstance" });
|
|
22
|
+
});
|
|
23
|
+
it("should use existing connection from Container", () => {
|
|
24
|
+
const existingConn = { mock: "existingKnex" };
|
|
25
|
+
typedi_1.Container.set("KnexConnection", existingConn);
|
|
26
|
+
const db = new kenx_provider_1.DB();
|
|
27
|
+
expect(db.client).toBe(existingConn);
|
|
28
|
+
});
|
|
29
|
+
// it("should not reinitialize if connection exists", () => {
|
|
30
|
+
// const db = new DB();
|
|
31
|
+
// const config = { client: "sqlite3", connection: {} };
|
|
32
|
+
// db.init(config as Knex.Config);
|
|
33
|
+
// const conn2 = db.init(config as Knex.Config);
|
|
34
|
+
// expect(conn2).toEqual({ mock: "knexInstance" });
|
|
35
|
+
// });
|
|
36
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const logger_1 = require("./logger");
|
|
7
|
+
const pino_1 = __importDefault(require("pino"));
|
|
8
|
+
jest.mock("pino");
|
|
9
|
+
describe("LoggerService constructor", () => {
|
|
10
|
+
const mockPino = pino_1.default;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockPino.mockClear();
|
|
13
|
+
});
|
|
14
|
+
it("should initialize logger with default level 'info' when LOG_LEVEL is not set", () => {
|
|
15
|
+
delete process.env.LOG_LEVEL;
|
|
16
|
+
new logger_1.LoggerService();
|
|
17
|
+
expect(mockPino).toHaveBeenCalledWith({
|
|
18
|
+
level: "info",
|
|
19
|
+
transport: {
|
|
20
|
+
target: "pino-pretty",
|
|
21
|
+
options: {
|
|
22
|
+
translateTime: "SYS:standard",
|
|
23
|
+
ignore: "pid,hostname",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it("should initialize logger with LOG_LEVEL from environment", () => {
|
|
29
|
+
process.env.LOG_LEVEL = "debug";
|
|
30
|
+
new logger_1.LoggerService();
|
|
31
|
+
expect(mockPino).toHaveBeenCalledWith({
|
|
32
|
+
level: "debug",
|
|
33
|
+
transport: {
|
|
34
|
+
target: "pino-pretty",
|
|
35
|
+
options: {
|
|
36
|
+
translateTime: "SYS:standard",
|
|
37
|
+
ignore: "pid,hostname",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
package/dist/middleware.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ export type AuthReturnTypes = IRequest | Promise<IRequest>;
|
|
|
14
14
|
interface AuthorizeClass {
|
|
15
15
|
authorize(req: IRequest, options?: any): AuthReturnTypes;
|
|
16
16
|
}
|
|
17
|
+
export declare function CanAuthorize(target: {
|
|
18
|
+
new (...args: any[]): AuthorizeClass;
|
|
19
|
+
}): void;
|
|
17
20
|
export declare function AppAuthorization(target: {
|
|
18
21
|
new (...args: any[]): AuthorizeClass;
|
|
19
22
|
}): void;
|
package/dist/middleware.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AuthorizeMiddleware = exports.AvleonMiddleware = void 0;
|
|
4
|
+
exports.CanAuthorize = CanAuthorize;
|
|
4
5
|
exports.AppAuthorization = AppAuthorization;
|
|
5
6
|
exports.Authorized = Authorized;
|
|
6
7
|
exports.AppMiddleware = AppMiddleware;
|
|
@@ -19,6 +20,12 @@ exports.AvleonMiddleware = AvleonMiddleware;
|
|
|
19
20
|
class AuthorizeMiddleware {
|
|
20
21
|
}
|
|
21
22
|
exports.AuthorizeMiddleware = AuthorizeMiddleware;
|
|
23
|
+
function CanAuthorize(target) {
|
|
24
|
+
if (typeof target.prototype.authorize !== "function") {
|
|
25
|
+
throw new Error(`Class "${target.name}" must implement an "authorize" method.`);
|
|
26
|
+
}
|
|
27
|
+
(0, typedi_1.Service)()(target);
|
|
28
|
+
}
|
|
22
29
|
function AppAuthorization(target) {
|
|
23
30
|
if (typeof target.prototype.authorize !== "function") {
|
|
24
31
|
throw new Error(`Class "${target.name}" must implement an "authorize" method.`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "reflect-metadata";
|