@avleon/core 0.0.40 → 0.0.43
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 +37 -5
- 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 +8 -5
- package/dist/icore.js +225 -81
- 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.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.d.ts +16 -14
- package/dist/queue.js +76 -63
- 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
|
@@ -59,9 +59,12 @@ const multipart_1 = __importDefault(require("@fastify/multipart"));
|
|
|
59
59
|
const validation_1 = require("./validation");
|
|
60
60
|
const utils_1 = require("./utils");
|
|
61
61
|
const socket_io_1 = require("socket.io");
|
|
62
|
+
const event_dispatcher_1 = require("./event-dispatcher");
|
|
62
63
|
const event_subscriber_1 = require("./event-subscriber");
|
|
63
64
|
const stream_1 = __importDefault(require("stream"));
|
|
64
65
|
const kenx_provider_1 = require("./kenx-provider");
|
|
66
|
+
const controller_1 = require("./controller");
|
|
67
|
+
const mime_1 = __importDefault(require("mime"));
|
|
65
68
|
const isTsNode = process.env.TS_NODE_DEV ||
|
|
66
69
|
process.env.TS_NODE_PROJECT ||
|
|
67
70
|
process[Symbol.for("ts-node.register.instance")];
|
|
@@ -112,6 +115,17 @@ class AvleonApplication {
|
|
|
112
115
|
}
|
|
113
116
|
return AvleonApplication.instance;
|
|
114
117
|
}
|
|
118
|
+
useLogger(corsOptions) {
|
|
119
|
+
let coptions = {};
|
|
120
|
+
if (corsOptions) {
|
|
121
|
+
if (this._isConfigClass(corsOptions)) {
|
|
122
|
+
coptions = this.appConfig.get(corsOptions);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
coptions = corsOptions;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
115
129
|
isDevelopment() {
|
|
116
130
|
const env = container_1.default.get(environment_variables_1.Environment);
|
|
117
131
|
return env.get("NODE_ENV") == "development";
|
|
@@ -128,7 +142,7 @@ class AvleonApplication {
|
|
|
128
142
|
if (options.ui && options.ui == "scalar") {
|
|
129
143
|
const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
|
|
130
144
|
failOnMissing: true,
|
|
131
|
-
customMessage:
|
|
145
|
+
customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
|
|
132
146
|
});
|
|
133
147
|
await this.app.register(scalarPlugin, {
|
|
134
148
|
routePrefix: rPrefix,
|
|
@@ -147,7 +161,7 @@ class AvleonApplication {
|
|
|
147
161
|
else {
|
|
148
162
|
const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
|
|
149
163
|
failOnMissing: true,
|
|
150
|
-
customMessage:
|
|
164
|
+
customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
|
|
151
165
|
});
|
|
152
166
|
await this.app.register(fastifySwaggerUi, {
|
|
153
167
|
logo: logo ? logo : null,
|
|
@@ -178,14 +192,16 @@ class AvleonApplication {
|
|
|
178
192
|
this._hasWebsocket = true;
|
|
179
193
|
this._initWebSocket(socketOptions);
|
|
180
194
|
}
|
|
195
|
+
useSocketIO(socketOptions) {
|
|
196
|
+
this._hasWebsocket = true;
|
|
197
|
+
this._initWebSocket(socketOptions);
|
|
198
|
+
}
|
|
181
199
|
async _initWebSocket(options) {
|
|
182
200
|
const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
|
|
183
201
|
failOnMissing: true,
|
|
184
|
-
customMessage:
|
|
202
|
+
customMessage: 'Install "fastify-socket.io" to enable socket.io.\n\n run pnpm install fastify-socket.io',
|
|
185
203
|
});
|
|
186
|
-
|
|
187
|
-
const socketIO = await this.app.io;
|
|
188
|
-
typedi_1.default.set(socket_io_1.Server, socketIO);
|
|
204
|
+
this.app.register(fsSocketIO, options);
|
|
189
205
|
}
|
|
190
206
|
useOpenApi(configOrClass) {
|
|
191
207
|
let openApiConfig;
|
|
@@ -209,8 +225,8 @@ class AvleonApplication {
|
|
|
209
225
|
if (multipartOptions) {
|
|
210
226
|
this.multipartOptions = multipartOptions;
|
|
211
227
|
this.app.register(multipart_1.default, {
|
|
212
|
-
...this.multipartOptions,
|
|
213
228
|
attachFieldsToBody: true,
|
|
229
|
+
...this.multipartOptions,
|
|
214
230
|
});
|
|
215
231
|
}
|
|
216
232
|
}
|
|
@@ -281,6 +297,7 @@ class AvleonApplication {
|
|
|
281
297
|
* @returns void
|
|
282
298
|
*/
|
|
283
299
|
async buildController(controller) {
|
|
300
|
+
var _a, _b;
|
|
284
301
|
const ctrl = typedi_1.default.get(controller);
|
|
285
302
|
const controllerMeta = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, ctrl.constructor);
|
|
286
303
|
if (!controllerMeta)
|
|
@@ -318,10 +335,39 @@ class AvleonApplication {
|
|
|
318
335
|
if (!swaggerMeta.body && bodySchema) {
|
|
319
336
|
schema = { ...schema, body: bodySchema };
|
|
320
337
|
}
|
|
338
|
+
// const isMultipart =
|
|
339
|
+
// schema.consumes?.includes('multipart/form-data') ||
|
|
340
|
+
// (schema.body && schema.body.type === 'object' &&
|
|
341
|
+
// Object.values(schema.body.properties || {}).some((p: any) => p.format === 'binary'));
|
|
342
|
+
const isMultipart = ((_a = schema === null || schema === void 0 ? void 0 : schema.consumes) === null || _a === void 0 ? void 0 : _a.includes("multipart/form-data")) ||
|
|
343
|
+
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");
|
|
344
|
+
// Prepare the route schema
|
|
345
|
+
let routeSchema = schema;
|
|
346
|
+
if (isMultipart) {
|
|
347
|
+
schema.consumes = ["multipart/form-data"];
|
|
348
|
+
if (!schema.body) {
|
|
349
|
+
schema.body = {
|
|
350
|
+
type: "object",
|
|
351
|
+
properties: {},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
for (const param of allMeta.body) {
|
|
355
|
+
if (param.type == "route:file") {
|
|
356
|
+
schema.body.properties[param.key] = {
|
|
357
|
+
type: "string",
|
|
358
|
+
format: "binary",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
schema.body.properties[param.key] = { type: param.dataType };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
321
366
|
this.app.route({
|
|
322
367
|
url: routePath,
|
|
323
368
|
method: methodmetaOptions.method.toUpperCase(),
|
|
324
|
-
schema:
|
|
369
|
+
schema: routeSchema,
|
|
370
|
+
attachValidation: isMultipart,
|
|
325
371
|
handler: async (req, res) => {
|
|
326
372
|
let reqClone = req;
|
|
327
373
|
// class level authrization
|
|
@@ -368,23 +414,25 @@ class AvleonApplication {
|
|
|
368
414
|
(0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: "queryparam" });
|
|
369
415
|
}
|
|
370
416
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
code
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
417
|
+
if (!isMultipart) {
|
|
418
|
+
for (let bodyMeta of allMeta.body) {
|
|
419
|
+
if (bodyMeta.validatorClass) {
|
|
420
|
+
const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
|
|
421
|
+
if (err) {
|
|
422
|
+
return await res.code(400).send({
|
|
423
|
+
code: 400,
|
|
424
|
+
error: "ValidationError",
|
|
425
|
+
errors: err,
|
|
426
|
+
message: err.message,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
381
429
|
}
|
|
382
430
|
}
|
|
383
431
|
}
|
|
384
432
|
const result = await prototype[method].apply(ctrl, args);
|
|
385
433
|
// Custom wrapped file download
|
|
386
|
-
if (result === null || result === void 0 ? void 0 : result.
|
|
387
|
-
const { stream, filename
|
|
434
|
+
if (result === null || result === void 0 ? void 0 : result.download) {
|
|
435
|
+
const { stream, filename } = result;
|
|
388
436
|
if (!stream || typeof stream.pipe !== "function") {
|
|
389
437
|
return res.code(500).send({
|
|
390
438
|
code: 500,
|
|
@@ -392,6 +440,9 @@ class AvleonApplication {
|
|
|
392
440
|
message: "Invalid stream object",
|
|
393
441
|
});
|
|
394
442
|
}
|
|
443
|
+
const contentType = result.contentType ||
|
|
444
|
+
mime_1.default.getType(filename) ||
|
|
445
|
+
"application/octet-stream";
|
|
395
446
|
res.header("Content-Type", contentType);
|
|
396
447
|
res.header("Content-Disposition", `attachment; filename="${filename}"`);
|
|
397
448
|
stream.on("error", (err) => {
|
|
@@ -427,61 +478,128 @@ class AvleonApplication {
|
|
|
427
478
|
}
|
|
428
479
|
}
|
|
429
480
|
/**
|
|
430
|
-
*
|
|
431
|
-
* @param req
|
|
432
|
-
* @param meta
|
|
433
|
-
* @returns
|
|
481
|
+
* Maps request data to controller method arguments based on decorators
|
|
482
|
+
* @param req - The incoming request object
|
|
483
|
+
* @param meta - Metadata about method parameters
|
|
484
|
+
* @returns Array of arguments to pass to the controller method
|
|
434
485
|
*/
|
|
435
486
|
async _mapArgs(req, meta) {
|
|
436
|
-
var _a;
|
|
487
|
+
var _a, _b, _c, _d;
|
|
488
|
+
// Initialize args cache on request if not present
|
|
437
489
|
if (!req.hasOwnProperty("_argsCache")) {
|
|
438
490
|
Object.defineProperty(req, "_argsCache", {
|
|
439
491
|
value: new Map(),
|
|
440
492
|
enumerable: false,
|
|
493
|
+
writable: false,
|
|
494
|
+
configurable: false,
|
|
441
495
|
});
|
|
442
496
|
}
|
|
443
497
|
const cache = req._argsCache;
|
|
444
|
-
const cacheKey = JSON.stringify(meta);
|
|
498
|
+
const cacheKey = JSON.stringify(meta);
|
|
499
|
+
// Return cached result if available
|
|
445
500
|
if (cache.has(cacheKey)) {
|
|
446
501
|
return cache.get(cacheKey);
|
|
447
502
|
}
|
|
448
|
-
|
|
449
|
-
meta.query.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
meta.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
503
|
+
// Initialize args array with correct length
|
|
504
|
+
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;
|
|
505
|
+
const args = new Array(maxIndex).fill(undefined);
|
|
506
|
+
// Map route parameters
|
|
507
|
+
meta.params.forEach((p) => {
|
|
508
|
+
args[p.index] =
|
|
509
|
+
p.key == "all" ? { ...req.query } : req.params[p.key] || null;
|
|
510
|
+
});
|
|
511
|
+
// Map query parameters
|
|
512
|
+
meta.query.forEach((q) => {
|
|
513
|
+
args[q.index] = q.key == "all" ? { ...req.query } : req.query[q.key];
|
|
514
|
+
});
|
|
515
|
+
// Map body data (including form data)
|
|
516
|
+
meta.body.forEach((body) => {
|
|
517
|
+
args[body.index] = { ...req.body, ...req.formData };
|
|
518
|
+
});
|
|
519
|
+
// Map current user
|
|
520
|
+
meta.currentUser.forEach((user) => {
|
|
521
|
+
args[user.index] = req.user;
|
|
522
|
+
});
|
|
523
|
+
// Map headers
|
|
524
|
+
meta.headers.forEach((header) => {
|
|
525
|
+
args[header.index] =
|
|
526
|
+
header.key === "all" ? { ...req.headers } : req.headers[header.key];
|
|
527
|
+
});
|
|
528
|
+
// Map request object
|
|
529
|
+
if (meta.request && meta.request.length > 0) {
|
|
530
|
+
meta.request.forEach((r) => {
|
|
531
|
+
args[r.index] = req;
|
|
532
|
+
});
|
|
458
533
|
}
|
|
459
|
-
|
|
460
|
-
|
|
534
|
+
// Handle file uploads (single or multiple files)
|
|
535
|
+
const needsFiles = (meta.file && meta.file.length > 0) ||
|
|
536
|
+
(meta.files && meta.files.length > 0);
|
|
537
|
+
if (needsFiles &&
|
|
538
|
+
((_d = req.headers["content-type"]) === null || _d === void 0 ? void 0 : _d.startsWith("multipart/form-data"))) {
|
|
461
539
|
const files = await req.saveRequestFiles();
|
|
462
540
|
if (!files || files.length === 0) {
|
|
463
|
-
|
|
541
|
+
// Only throw error if files are explicitly required
|
|
542
|
+
if (meta.files && meta.files.length > 0) {
|
|
543
|
+
throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
|
|
544
|
+
}
|
|
545
|
+
// For single file (@File()), set to null
|
|
546
|
+
if (meta.file && meta.file.length > 0) {
|
|
547
|
+
meta.file.forEach((f) => {
|
|
548
|
+
args[f.index] = null;
|
|
549
|
+
});
|
|
550
|
+
}
|
|
464
551
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
552
|
+
else {
|
|
553
|
+
// Create file info objects
|
|
554
|
+
const fileInfo = files.map((file) => ({
|
|
555
|
+
type: file.type,
|
|
556
|
+
filepath: file.filepath,
|
|
557
|
+
fieldname: file.fieldname,
|
|
558
|
+
filename: file.filename,
|
|
559
|
+
encoding: file.encoding,
|
|
560
|
+
mimetype: file.mimetype,
|
|
561
|
+
fields: file.fields,
|
|
562
|
+
}));
|
|
563
|
+
// Handle single file decorator (@File())
|
|
564
|
+
if (meta.file && meta.file.length > 0) {
|
|
565
|
+
meta.file.forEach((f) => {
|
|
566
|
+
if (f.fieldName === "all") {
|
|
567
|
+
// Return first file if "all" is specified
|
|
568
|
+
args[f.index] = fileInfo[0] || null;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// Find specific file by fieldname
|
|
572
|
+
const file = fileInfo.find((x) => x.fieldname === f.fieldName);
|
|
573
|
+
if (!file) {
|
|
574
|
+
throw new exceptions_1.BadRequestException(`File field "${f.fieldName}" not found in uploaded files`);
|
|
575
|
+
}
|
|
576
|
+
args[f.index] = file;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (meta.files && meta.files.length > 0) {
|
|
581
|
+
meta.files.forEach((f) => {
|
|
582
|
+
if (f.fieldName === "all") {
|
|
583
|
+
args[f.index] = fileInfo;
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
const matchingFiles = fileInfo.filter((x) => x.fieldname === f.fieldName);
|
|
587
|
+
if (matchingFiles.length === 0) {
|
|
588
|
+
throw new exceptions_1.BadRequestException(`No files found for field "${f.fieldName}"`);
|
|
589
|
+
}
|
|
590
|
+
args[f.index] = matchingFiles;
|
|
591
|
+
}
|
|
592
|
+
});
|
|
478
593
|
}
|
|
479
|
-
args[f.index] =
|
|
480
|
-
f.fieldName == "all"
|
|
481
|
-
? fileInfo
|
|
482
|
-
: fileInfo.filter((x) => x.fieldname == f.fieldName);
|
|
483
594
|
}
|
|
484
595
|
}
|
|
596
|
+
else if (needsFiles) {
|
|
597
|
+
// Files expected but request is not multipart/form-data
|
|
598
|
+
throw new exceptions_1.BadRequestException({
|
|
599
|
+
error: "Invalid content type. Expected multipart/form-data for file uploads",
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
// Cache the result
|
|
485
603
|
cache.set(cacheKey, args);
|
|
486
604
|
return args;
|
|
487
605
|
}
|
|
@@ -497,6 +615,7 @@ class AvleonApplication {
|
|
|
497
615
|
return this.metaCache.get(cacheKey);
|
|
498
616
|
}
|
|
499
617
|
const meta = {
|
|
618
|
+
request: Reflect.getMetadata(controller_1.REQUEST_METADATA_KEY, prototype, method) || [],
|
|
500
619
|
params: Reflect.getMetadata(container_1.PARAM_META_KEY, prototype, method) || [],
|
|
501
620
|
query: Reflect.getMetadata(container_1.QUERY_META_KEY, prototype, method) || [],
|
|
502
621
|
body: Reflect.getMetadata(container_1.REQUEST_BODY_META_KEY, prototype, method) || [],
|
|
@@ -557,7 +676,7 @@ class AvleonApplication {
|
|
|
557
676
|
if (this.controllers.length > 0) {
|
|
558
677
|
for (let controller of this.controllers) {
|
|
559
678
|
if ((0, container_1.isApiController)(controller)) {
|
|
560
|
-
this.buildController(controller);
|
|
679
|
+
await this.buildController(controller);
|
|
561
680
|
}
|
|
562
681
|
else {
|
|
563
682
|
throw new system_exception_1.SystemUseError("Not a api controller.");
|
|
@@ -657,7 +776,15 @@ class AvleonApplication {
|
|
|
657
776
|
}
|
|
658
777
|
}
|
|
659
778
|
handleSocket(socket) {
|
|
779
|
+
const contextService = typedi_1.default.get(event_dispatcher_1.SocketContextService);
|
|
660
780
|
subscriberRegistry.register(socket);
|
|
781
|
+
// Wrap all future event handlers with context
|
|
782
|
+
const originalOn = socket.on.bind(socket);
|
|
783
|
+
socket.on = (event, handler) => {
|
|
784
|
+
return originalOn(event, (...args) => {
|
|
785
|
+
contextService.run(socket, () => handler(...args));
|
|
786
|
+
});
|
|
787
|
+
};
|
|
661
788
|
}
|
|
662
789
|
async run(port = 4000, fn) {
|
|
663
790
|
if (this.alreadyRun)
|
|
@@ -677,7 +804,7 @@ class AvleonApplication {
|
|
|
677
804
|
this.rMap.forEach((value, key) => {
|
|
678
805
|
const [m, r] = key.split(":");
|
|
679
806
|
this.app.route({
|
|
680
|
-
method: m,
|
|
807
|
+
method: m.toUpperCase(),
|
|
681
808
|
url: r,
|
|
682
809
|
schema: value.schema || {},
|
|
683
810
|
preHandler: value.middlewares ? value.middlewares : [],
|
|
@@ -687,32 +814,49 @@ class AvleonApplication {
|
|
|
687
814
|
},
|
|
688
815
|
});
|
|
689
816
|
});
|
|
690
|
-
this.app.setErrorHandler(
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
817
|
+
this.app.setErrorHandler((error, request, reply) => {
|
|
818
|
+
if (error instanceof exceptions_1.BaseHttpException) {
|
|
819
|
+
const response = {
|
|
820
|
+
code: error.code,
|
|
821
|
+
status: "Error",
|
|
822
|
+
data: error.payload,
|
|
823
|
+
};
|
|
824
|
+
return reply
|
|
825
|
+
.status(error.code || 500)
|
|
826
|
+
.type("application/json")
|
|
827
|
+
.serializer((payload) => JSON.stringify(payload))
|
|
828
|
+
.send(response);
|
|
699
829
|
}
|
|
700
|
-
return
|
|
830
|
+
return reply.status(500).send({
|
|
831
|
+
code: 500,
|
|
832
|
+
message: error.message || "Internal Server Error",
|
|
833
|
+
});
|
|
701
834
|
});
|
|
702
835
|
await this.app.ready();
|
|
703
836
|
if (this._hasWebsocket) {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
837
|
+
if (!this.app.io) {
|
|
838
|
+
throw new Error("Socket.IO not initialized. Make sure fastify-socket.io is registered correctly.");
|
|
839
|
+
}
|
|
840
|
+
// Register the io instance in Container
|
|
841
|
+
typedi_1.default.set(socket_io_1.Server, this.app.io);
|
|
842
|
+
// Register middleware first
|
|
843
|
+
// await this.app.io.use(
|
|
844
|
+
// (
|
|
845
|
+
// socket: { handshake: { auth: { token: any } }; data: { user: any } },
|
|
846
|
+
// next: any,
|
|
847
|
+
// ) => {
|
|
848
|
+
// const token = socket.handshake.auth.token;
|
|
849
|
+
// try {
|
|
850
|
+
// const user = { id: 1, name: "tareq" };
|
|
851
|
+
// socket.data.user = user; // this powers @AuthUser()
|
|
852
|
+
// next();
|
|
853
|
+
// } catch {
|
|
854
|
+
// next(new Error("Unauthorized"));
|
|
855
|
+
// }
|
|
856
|
+
// },
|
|
857
|
+
// );
|
|
858
|
+
// Then register connection handler
|
|
859
|
+
await this.app.io.on("connection", this.handleSocket.bind(this));
|
|
716
860
|
}
|
|
717
861
|
await this.app.listen({ port });
|
|
718
862
|
console.log(`Application running on http://127.0.0.1:${port}`);
|
|
@@ -723,7 +867,7 @@ class AvleonApplication {
|
|
|
723
867
|
this.rMap.forEach((value, key) => {
|
|
724
868
|
const [m, r] = key.split(":");
|
|
725
869
|
this.app.route({
|
|
726
|
-
method: m,
|
|
870
|
+
method: m.toUpperCase(),
|
|
727
871
|
url: r,
|
|
728
872
|
schema: value.schema || {},
|
|
729
873
|
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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "reflect-metadata";
|