@avleon/core 0.0.24 → 0.0.26
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/dist/application.d.ts +21 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +78 -0
- package/dist/exceptions/http-exceptions.d.ts +9 -1
- package/dist/exceptions/http-exceptions.js +9 -9
- package/dist/icore.d.ts +32 -78
- package/dist/icore.js +118 -117
- package/dist/interfaces/avleon-application.d.ts +27 -0
- package/dist/interfaces/avleon-application.js +1 -0
- package/dist/middleware.d.ts +1 -1
- package/dist/response.d.ts +0 -10
- package/dist/response.js +1 -28
- package/dist/testing.js +1 -1
- package/package.json +21 -12
- package/src/application.ts +29 -0
- package/src/cache.ts +91 -0
- package/src/config.ts +3 -0
- package/src/controller.ts +1 -2
- package/src/exceptions/http-exceptions.ts +10 -10
- package/src/icore.ts +178 -158
- package/src/interfaces/avleon-application.ts +40 -0
- package/src/middleware.ts +1 -1
- package/src/response.ts +0 -43
- package/src/testing.ts +1 -1
package/dist/icore.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
39
|
+
exports.Avleon = exports.AvleonTest = exports.AvleonApplication = void 0;
|
|
40
40
|
/**
|
|
41
41
|
* @copyright 2024
|
|
42
42
|
* @author Tareq Hossain
|
|
@@ -50,7 +50,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
50
50
|
const container_1 = __importStar(require("./container"));
|
|
51
51
|
const helpers_1 = require("./helpers");
|
|
52
52
|
const system_exception_1 = require("./exceptions/system-exception");
|
|
53
|
-
const fs_1 = require("fs");
|
|
54
53
|
const exceptions_1 = require("./exceptions");
|
|
55
54
|
const swagger_1 = __importDefault(require("@fastify/swagger"));
|
|
56
55
|
const config_1 = require("./config");
|
|
@@ -78,11 +77,12 @@ class AvleonApplication {
|
|
|
78
77
|
this.authorizeMiddleware = undefined;
|
|
79
78
|
this.dataSource = undefined;
|
|
80
79
|
this.isMapFeatures = false;
|
|
80
|
+
this.registerControllerAuto = false;
|
|
81
|
+
this.registerControllerPath = './src';
|
|
81
82
|
this.metaCache = new Map();
|
|
82
83
|
this.app = (0, fastify_1.default)();
|
|
83
84
|
this.appConfig = new config_1.AppConfig();
|
|
84
85
|
}
|
|
85
|
-
isTest() { }
|
|
86
86
|
static getApp() {
|
|
87
87
|
let isTestEnv = process.env.NODE_ENV == "test";
|
|
88
88
|
if (!AvleonApplication.instance) {
|
|
@@ -150,75 +150,69 @@ class AvleonApplication {
|
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
_isConfigClass(input) {
|
|
154
|
+
var _a;
|
|
155
|
+
return (typeof input === 'function' &&
|
|
156
|
+
typeof input.prototype === 'object' &&
|
|
157
|
+
((_a = input.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === input);
|
|
158
|
+
}
|
|
159
|
+
useCors(corsOptions) {
|
|
160
|
+
let coptions = {};
|
|
161
|
+
if (corsOptions) {
|
|
162
|
+
if (this._isConfigClass(corsOptions)) {
|
|
163
|
+
coptions = this.appConfig.get(corsOptions);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
coptions = corsOptions;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this.app.register(cors_1.default, coptions);
|
|
155
170
|
}
|
|
156
|
-
useOpenApi(
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
this.globalSwaggerOptions = modifiedConfig;
|
|
171
|
+
useOpenApi(configOrClass) {
|
|
172
|
+
let openApiConfig;
|
|
173
|
+
if (this._isConfigClass(configOrClass)) {
|
|
174
|
+
openApiConfig = this.appConfig.get(configOrClass);
|
|
161
175
|
}
|
|
162
176
|
else {
|
|
163
|
-
|
|
177
|
+
openApiConfig = configOrClass;
|
|
164
178
|
}
|
|
179
|
+
this.globalSwaggerOptions = openApiConfig;
|
|
165
180
|
this.hasSwagger = true;
|
|
166
181
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Registers the fastify-multipart plugin with the Fastify instance.
|
|
169
|
-
* This enables handling of multipart/form-data requests, typically used for file uploads.
|
|
170
|
-
*
|
|
171
|
-
* @param {MultipartOptions} options - Options to configure the fastify-multipart plugin.
|
|
172
|
-
* @param {FastifyInstance} this.app - The Fastify instance to register the plugin with.
|
|
173
|
-
* @property {MultipartOptions} this.multipartOptions - Stores the provided multipart options.
|
|
174
|
-
* @see {@link https://github.com/fastify/fastify-multipart} for more details on available options.
|
|
175
|
-
*/
|
|
176
182
|
useMultipart(options) {
|
|
177
|
-
|
|
178
|
-
this.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
let multipartOptions;
|
|
184
|
+
if (this._isConfigClass(options)) {
|
|
185
|
+
multipartOptions = this.appConfig.get(options);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
multipartOptions = options;
|
|
189
|
+
}
|
|
190
|
+
if (multipartOptions) {
|
|
191
|
+
this.multipartOptions = multipartOptions;
|
|
192
|
+
this.app.register(multipart_1.default, {
|
|
193
|
+
...this.multipartOptions,
|
|
194
|
+
attachFieldsToBody: true,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
182
197
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
*
|
|
188
|
-
* @template T - A generic type extending the `IConfig` interface, representing the configuration class.
|
|
189
|
-
* @template R - A generic type representing the return type of the configuration method of the `ConfigClass`.
|
|
190
|
-
* @param {Constructable<T>} ConfigClass - The constructor of the configuration class to be used for creating the DataSource.
|
|
191
|
-
* @param {(config: R) => R} [modifyConfig] - An optional function that takes the initial configuration and returns a modified configuration.
|
|
192
|
-
* @returns {void}
|
|
193
|
-
* @property {DataSourceOptions} this.dataSourceOptions - Stores the final DataSource options after potential modification.
|
|
194
|
-
* @property {DataSource} this.dataSource - Stores the initialized TypeORM DataSource instance.
|
|
195
|
-
* @see {@link https://typeorm.io/} for more information about TypeORM.
|
|
196
|
-
*/
|
|
197
|
-
useDataSource(ConfigClass, modifyConfig) {
|
|
198
|
-
const dsConfig = this.appConfig.get(ConfigClass);
|
|
199
|
-
if (modifyConfig) {
|
|
200
|
-
const modifiedConfig = modifyConfig(dsConfig);
|
|
201
|
-
this.dataSourceOptions = modifiedConfig;
|
|
198
|
+
useDataSource(options) {
|
|
199
|
+
let dataSourceOptions;
|
|
200
|
+
if (this._isConfigClass(options)) {
|
|
201
|
+
dataSourceOptions = this.appConfig.get(options);
|
|
202
202
|
}
|
|
203
203
|
else {
|
|
204
|
-
|
|
204
|
+
dataSourceOptions = options;
|
|
205
205
|
}
|
|
206
|
+
if (!dataSourceOptions)
|
|
207
|
+
throw new system_exception_1.SystemUseError("Invlaid datasource options.");
|
|
208
|
+
this.dataSourceOptions = dataSourceOptions;
|
|
206
209
|
const typeorm = require("typeorm");
|
|
207
|
-
const datasource = new typeorm.DataSource(
|
|
210
|
+
const datasource = new typeorm.DataSource(dataSourceOptions);
|
|
208
211
|
this.dataSource = datasource;
|
|
209
212
|
typedi_1.default.set("idatasource", datasource);
|
|
210
213
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
* It retrieves instances of the middleware classes from the dependency injection container
|
|
214
|
-
* and adds them as 'preHandler' hooks to the Fastify application.
|
|
215
|
-
*
|
|
216
|
-
* @template T - A generic type extending the `AppMiddleware` interface, representing the middleware class.
|
|
217
|
-
* @param {Constructor<T>[]} mclasses - An array of middleware class constructors to be registered.
|
|
218
|
-
* @returns {void}
|
|
219
|
-
* @property {Map<string, T>} this.middlewares - Stores the registered middleware instances, keyed by their class names.
|
|
220
|
-
* @see {@link https://www.fastify.io/docs/latest/Reference/Hooks/#prehandler} for more information about Fastify preHandler hooks.
|
|
221
|
-
*/
|
|
214
|
+
_useCache(options) {
|
|
215
|
+
}
|
|
222
216
|
useMiddlewares(mclasses) {
|
|
223
217
|
for (const mclass of mclasses) {
|
|
224
218
|
const cls = typedi_1.default.get(mclass);
|
|
@@ -226,28 +220,9 @@ class AvleonApplication {
|
|
|
226
220
|
this.app.addHook("preHandler", cls.invoke);
|
|
227
221
|
}
|
|
228
222
|
}
|
|
229
|
-
/**
|
|
230
|
-
* Registers a middleware constructor to be used for authorization purposes.
|
|
231
|
-
* The specific implementation and usage of this middleware will depend on the application's authorization logic.
|
|
232
|
-
*
|
|
233
|
-
* @template T - A generic type representing the constructor of the authorization middleware.
|
|
234
|
-
* @param {Constructor<T>} middleware - The constructor of the middleware to be used for authorization.
|
|
235
|
-
* @returns {void}
|
|
236
|
-
* @property {any} this.authorizeMiddleware - Stores the constructor of the authorization middleware.
|
|
237
|
-
*/
|
|
238
223
|
useAuthoriztion(middleware) {
|
|
239
224
|
this.authorizeMiddleware = middleware;
|
|
240
225
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Registers the `@fastify/static` plugin to serve static files.
|
|
243
|
-
* It configures the root directory and URL prefix for serving static assets.
|
|
244
|
-
*
|
|
245
|
-
* @param {StaticFileOptions} [options={ path: undefined, prefix: undefined }] - Optional configuration for serving static files.
|
|
246
|
-
* @param {string} [options.path] - The absolute path to the static files directory. Defaults to 'process.cwd()/public'.
|
|
247
|
-
* @param {string} [options.prefix] - The URL prefix for serving static files. Defaults to '/static/'.
|
|
248
|
-
* @returns {void}
|
|
249
|
-
* @see {@link https://github.com/fastify/fastify-static} for more details on available options.
|
|
250
|
-
*/
|
|
251
226
|
useStaticFiles(options = { path: undefined, prefix: undefined }) {
|
|
252
227
|
this.app.register(require("@fastify/static"), {
|
|
253
228
|
root: options.path ? options.path : path_1.default.join(process.cwd(), "public"),
|
|
@@ -463,32 +438,59 @@ class AvleonApplication {
|
|
|
463
438
|
this.metaCache.set(cacheKey, meta);
|
|
464
439
|
return meta;
|
|
465
440
|
}
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
441
|
+
_resolveControllerDir(dir) {
|
|
442
|
+
const isTsNode = process.env.TS_NODE_DEV ||
|
|
443
|
+
process.env.TS_NODE_PROJECT ||
|
|
444
|
+
process[Symbol.for("ts-node.register.instance")];
|
|
445
|
+
const controllerDir = path_1.default.join(process.cwd(), this.registerControllerPath);
|
|
446
|
+
return isTsNode ? controllerDir : controllerDir.replace('src', 'dist');
|
|
447
|
+
}
|
|
448
|
+
async autoControllers(controllersPath) {
|
|
449
|
+
const conDir = this._resolveControllerDir(controllersPath);
|
|
450
|
+
const files = await promises_1.default.readdir(conDir, { recursive: true });
|
|
469
451
|
for (const file of files) {
|
|
452
|
+
const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
|
|
453
|
+
if (isTestFile)
|
|
454
|
+
continue;
|
|
470
455
|
if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
|
|
471
|
-
const filePath = path_1.default.join(
|
|
456
|
+
const filePath = path_1.default.join(conDir, file);
|
|
472
457
|
const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
|
|
473
458
|
for (const exported of Object.values(module)) {
|
|
474
459
|
if (typeof exported === "function" && (0, container_1.isApiController)(exported)) {
|
|
475
|
-
|
|
460
|
+
console.log('adding', exported.name);
|
|
461
|
+
if (!this.controllers.some(con => exported.name == con.name)) {
|
|
462
|
+
this.controllers.push(exported);
|
|
463
|
+
}
|
|
464
|
+
//this.buildController(exported);
|
|
476
465
|
}
|
|
477
466
|
}
|
|
478
467
|
}
|
|
479
468
|
}
|
|
480
469
|
}
|
|
481
|
-
|
|
482
|
-
|
|
470
|
+
useControllers(controllers) {
|
|
471
|
+
if (Array.isArray(controllers)) {
|
|
472
|
+
this.controllers = controllers;
|
|
473
|
+
controllers.forEach(controller => {
|
|
474
|
+
if (!this.controllers.includes(controller)) {
|
|
475
|
+
this.controllers.push(controller);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
this.registerControllerAuto = true;
|
|
481
|
+
if (controllers.path) {
|
|
482
|
+
this.registerControllerPath = controllers.path;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
483
485
|
}
|
|
484
486
|
// addFeature(feature:{controllers:Function[]}){
|
|
485
487
|
// feature.controllers.forEach(c=> this.controllers.push(c))
|
|
486
488
|
// }
|
|
487
|
-
mapFeature()
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
489
|
+
// mapFeature(){
|
|
490
|
+
// if(!this.isMapFeatures){
|
|
491
|
+
// this.isMapFeatures = true;
|
|
492
|
+
// }
|
|
493
|
+
// }
|
|
492
494
|
async _mapControllers() {
|
|
493
495
|
if (this.controllers.length > 0) {
|
|
494
496
|
for (let controller of this.controllers) {
|
|
@@ -501,13 +503,10 @@ class AvleonApplication {
|
|
|
501
503
|
}
|
|
502
504
|
}
|
|
503
505
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
async handleRoute(args) { }
|
|
506
|
+
// useControllersAuto(controllerPath?:string) {
|
|
507
|
+
// this.registerControllerAuto = true;
|
|
508
|
+
// //this.autoControllers();
|
|
509
|
+
// }
|
|
511
510
|
async mapFn(fn) {
|
|
512
511
|
const original = fn;
|
|
513
512
|
fn = function () { };
|
|
@@ -556,7 +555,7 @@ class AvleonApplication {
|
|
|
556
555
|
middlewares: [],
|
|
557
556
|
schema: {},
|
|
558
557
|
});
|
|
559
|
-
this.mapFn(fn);
|
|
558
|
+
// this.mapFn(fn);
|
|
560
559
|
const route = {
|
|
561
560
|
useMiddleware: (middlewares) => {
|
|
562
561
|
const midds = Array.isArray(middlewares) ? middlewares : [middlewares];
|
|
@@ -613,6 +612,9 @@ class AvleonApplication {
|
|
|
613
612
|
if (this.isMapFeatures) {
|
|
614
613
|
this._mapFeatures();
|
|
615
614
|
}
|
|
615
|
+
if (this.registerControllerAuto) {
|
|
616
|
+
await this.autoControllers();
|
|
617
|
+
}
|
|
616
618
|
await this._mapControllers();
|
|
617
619
|
this.rMap.forEach((value, key) => {
|
|
618
620
|
const [m, r] = key.split(":");
|
|
@@ -680,7 +682,11 @@ class AvleonApplication {
|
|
|
680
682
|
patch: async (url, options) => this.app.inject({ method: "PATCH", url, ...options }),
|
|
681
683
|
delete: async (url, options) => this.app.inject({ method: "DELETE", url, ...options }),
|
|
682
684
|
options: async (url, options) => this.app.inject({ method: "OPTIONS", url, ...options }),
|
|
683
|
-
getController: (controller) => {
|
|
685
|
+
getController: (controller, deps = []) => {
|
|
686
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
687
|
+
deps.forEach((dep, i) => {
|
|
688
|
+
typedi_1.default.set(paramTypes[i], dep);
|
|
689
|
+
});
|
|
684
690
|
return typedi_1.default.get(controller);
|
|
685
691
|
},
|
|
686
692
|
};
|
|
@@ -693,44 +699,39 @@ class AvleonApplication {
|
|
|
693
699
|
}
|
|
694
700
|
exports.AvleonApplication = AvleonApplication;
|
|
695
701
|
AvleonApplication.buildOptions = {};
|
|
696
|
-
class
|
|
702
|
+
class AvleonTest {
|
|
697
703
|
constructor() {
|
|
698
704
|
process.env.NODE_ENV = "test";
|
|
699
705
|
}
|
|
700
|
-
static
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
addDatasource(options) {
|
|
707
|
-
this.dataSourceOptions = options;
|
|
708
|
-
}
|
|
709
|
-
getController(controller) {
|
|
706
|
+
static getController(controller, deps = []) {
|
|
707
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
708
|
+
deps.forEach((dep, i) => {
|
|
709
|
+
typedi_1.default.set(paramTypes[i], dep);
|
|
710
|
+
});
|
|
710
711
|
return typedi_1.default.get(controller);
|
|
711
712
|
}
|
|
712
713
|
getService(service) {
|
|
713
714
|
return typedi_1.default.get(service);
|
|
714
715
|
}
|
|
715
|
-
|
|
716
|
+
static createTestApplication(options) {
|
|
716
717
|
const app = AvleonApplication.getInternalApp({
|
|
717
|
-
dataSourceOptions:
|
|
718
|
+
dataSourceOptions: options.dataSource ? options.dataSource : null,
|
|
718
719
|
});
|
|
719
|
-
app.
|
|
720
|
+
app.useControllers([...options.controllers]);
|
|
720
721
|
return app.getTestApp();
|
|
721
722
|
}
|
|
722
|
-
|
|
723
|
+
static from(app) {
|
|
723
724
|
return app.getTestApp();
|
|
724
725
|
}
|
|
725
|
-
|
|
726
|
-
|
|
726
|
+
static clean() {
|
|
727
|
+
typedi_1.default.reset();
|
|
727
728
|
}
|
|
728
729
|
}
|
|
729
|
-
exports.
|
|
730
|
-
class
|
|
730
|
+
exports.AvleonTest = AvleonTest;
|
|
731
|
+
class Avleon {
|
|
731
732
|
static createApplication() {
|
|
732
733
|
const app = AvleonApplication.getApp();
|
|
733
734
|
return app;
|
|
734
735
|
}
|
|
735
736
|
}
|
|
736
|
-
exports.
|
|
737
|
+
exports.Avleon = Avleon;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface AvleonApplication {
|
|
2
|
+
useCors: () => void;
|
|
3
|
+
/**
|
|
4
|
+
* function for register database
|
|
5
|
+
* @param options datasource options. options can be plain object or avleon config class
|
|
6
|
+
* */
|
|
7
|
+
useDatasource: () => void;
|
|
8
|
+
useMultipart: () => void;
|
|
9
|
+
useOpenApi: () => void;
|
|
10
|
+
useMiddlewares: () => void;
|
|
11
|
+
useAuthorization: () => void;
|
|
12
|
+
useSerialization: () => void;
|
|
13
|
+
useControllers: () => void;
|
|
14
|
+
useStaticFiles: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* @experimental
|
|
17
|
+
* use https as defalut http protocol
|
|
18
|
+
* */
|
|
19
|
+
useHttps: () => void;
|
|
20
|
+
mapGet: () => void;
|
|
21
|
+
mapPost: () => void;
|
|
22
|
+
mapPut: () => void;
|
|
23
|
+
mapPatch: () => void;
|
|
24
|
+
mapOptions: () => void;
|
|
25
|
+
mapGroup: () => void;
|
|
26
|
+
run: () => void;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IRequest, IResponse } from "./icore";
|
|
2
|
-
import { HttpException } from "./exceptions";
|
|
2
|
+
import { HttpExceptionTypes as HttpException } from "./exceptions";
|
|
3
3
|
export declare abstract class AppMiddleware {
|
|
4
4
|
abstract invoke(req: IRequest, res?: IResponse): Promise<IRequest | HttpException>;
|
|
5
5
|
}
|
package/dist/response.d.ts
CHANGED
|
@@ -14,13 +14,3 @@ export declare class HttpResponse {
|
|
|
14
14
|
static Created<T>(obj: any, s?: ClassConstructor<T>): IHttpResponse<T>;
|
|
15
15
|
static NoContent(): IHttpResponse<null>;
|
|
16
16
|
}
|
|
17
|
-
export declare class HttpExceptions {
|
|
18
|
-
static BadRequest(message: string, data?: any): IHttpResponse<any>;
|
|
19
|
-
static Unauthorized(message: string, data?: any): IHttpResponse<any>;
|
|
20
|
-
static Forbidden(message: string, data?: any): IHttpResponse<any>;
|
|
21
|
-
static NotFound(message: string, data?: any): IHttpResponse<any>;
|
|
22
|
-
static InternalServerError(message?: string, data?: any): IHttpResponse<any>;
|
|
23
|
-
static Conflict(message: string, data?: any): IHttpResponse<any>;
|
|
24
|
-
static UnprocessableEntity(message: string, data?: any): IHttpResponse<any>;
|
|
25
|
-
static TooManyRequests(message: string, data?: any): IHttpResponse<any>;
|
|
26
|
-
}
|
package/dist/response.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.HttpResponse = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @copyright 2024
|
|
6
6
|
* @author Tareq Hossain
|
|
@@ -54,30 +54,3 @@ class HttpResponse {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
exports.HttpResponse = HttpResponse;
|
|
57
|
-
class HttpExceptions {
|
|
58
|
-
static BadRequest(message, data = null) {
|
|
59
|
-
return { message: message, data: data };
|
|
60
|
-
}
|
|
61
|
-
static Unauthorized(message, data = null) {
|
|
62
|
-
return { message: message, data: data };
|
|
63
|
-
}
|
|
64
|
-
static Forbidden(message, data = null) {
|
|
65
|
-
return { message: message, data: data };
|
|
66
|
-
}
|
|
67
|
-
static NotFound(message, data = null) {
|
|
68
|
-
return { message: message, data: data };
|
|
69
|
-
}
|
|
70
|
-
static InternalServerError(message = "Internal server error", data = null) {
|
|
71
|
-
return { message: message, data: data };
|
|
72
|
-
}
|
|
73
|
-
static Conflict(message, data = null) {
|
|
74
|
-
return { message: message, data: data };
|
|
75
|
-
}
|
|
76
|
-
static UnprocessableEntity(message, data = null) {
|
|
77
|
-
return { message: message, data: data };
|
|
78
|
-
}
|
|
79
|
-
static TooManyRequests(message, data = null) {
|
|
80
|
-
return { message: message, data: data };
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
exports.HttpExceptions = HttpExceptions;
|
package/dist/testing.js
CHANGED
|
@@ -115,7 +115,7 @@ class AvleonTestBuilder {
|
|
|
115
115
|
dataSourceOptions: this.testOptions.dataSourceOptions,
|
|
116
116
|
});
|
|
117
117
|
// Map controllers
|
|
118
|
-
app.
|
|
118
|
+
app.useControllers(this.controllers);
|
|
119
119
|
// Get test application
|
|
120
120
|
return app.getTestApp();
|
|
121
121
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
|
+
"types": ["./dist/icore.d.ts"],
|
|
5
6
|
"scripts": {
|
|
6
7
|
"build": "npm run clean && tsc",
|
|
7
|
-
"clean": "
|
|
8
|
+
"clean": "rimraf dist",
|
|
8
9
|
"watch": "tsc-watch",
|
|
9
10
|
"test": "jest",
|
|
10
11
|
"test:watch": "jest --watch"
|
|
11
12
|
},
|
|
12
|
-
"keywords": [],
|
|
13
|
+
"keywords": ["restapi", "avleon", "backend","fastify"],
|
|
13
14
|
"author": "Tareq Hossain",
|
|
14
15
|
"license": "ISC",
|
|
15
16
|
"devDependencies": {
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
"@types/node": "^20.17.25",
|
|
18
19
|
"class-transformer": "^0.5.1",
|
|
19
20
|
"class-validator": "^0.14.1",
|
|
21
|
+
"ioredis": "^5.6.1",
|
|
20
22
|
"jest": "^29.7.0",
|
|
21
23
|
"nodemon": "^3.1.7",
|
|
22
24
|
"sharp": "^0.33.5",
|
|
@@ -42,15 +44,22 @@
|
|
|
42
44
|
"reflect-metadata": "^0.2.2",
|
|
43
45
|
"typedi": "^0.10.0"
|
|
44
46
|
},
|
|
45
|
-
"peerDependencies": {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@scalar/fastify-api-reference": "*",
|
|
49
|
+
"ioredis": "*",
|
|
50
|
+
"sharp": "*"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@scalar/fastify-api-reference": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"ioredis": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"sharp": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
54
63
|
"directories": {
|
|
55
64
|
"test": "tests"
|
|
56
65
|
},
|
package/src/application.ts
CHANGED
|
@@ -2,7 +2,36 @@ import Container from "typedi";
|
|
|
2
2
|
import { Constructor } from "./helpers";
|
|
3
3
|
import fastify, { FastifyInstance, RouteShorthandMethod } from "fastify";
|
|
4
4
|
|
|
5
|
+
export interface AvleonApplication{
|
|
6
|
+
// all public
|
|
7
|
+
useCors: () => void
|
|
8
|
+
useOpenApi:() => void
|
|
9
|
+
useView:()=> void;
|
|
10
|
+
useAuth:() => void
|
|
11
|
+
useMultipart:()=> void
|
|
12
|
+
useDataSource: () => void
|
|
13
|
+
useMiddlewares: () => void
|
|
14
|
+
useControllers: () => void
|
|
15
|
+
useAutoControllers:() => void
|
|
16
|
+
useStaticFiles: () => void
|
|
17
|
+
useCustomErrorHandler: () => void
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// all mapping
|
|
23
|
+
mapGroup:() => any;
|
|
24
|
+
mapGet:()=> any;
|
|
25
|
+
mapPost:()=> any;
|
|
26
|
+
mapPut: () => any;
|
|
27
|
+
mapPatch: () => any;
|
|
28
|
+
mapDelete: () => any;
|
|
29
|
+
mapView: () => any;
|
|
30
|
+
|
|
31
|
+
// run
|
|
32
|
+
run:(port:number) => void
|
|
5
33
|
|
|
34
|
+
}
|
|
6
35
|
|
|
7
36
|
|
|
8
37
|
export interface InlineRoutes{
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
|
|
3
|
+
type CacheEntry<T = any> = {
|
|
4
|
+
data: T;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class CacheManager {
|
|
9
|
+
private store = new Map<string, CacheEntry>();
|
|
10
|
+
private tagsMap = new Map<string, Set<string>>();
|
|
11
|
+
private redis: Redis | null = null;
|
|
12
|
+
|
|
13
|
+
constructor(redisInstance?: Redis) {
|
|
14
|
+
this.redis = redisInstance || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private redisTagKey(tag: string) {
|
|
18
|
+
return `cache-tags:${tag}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get<T>(key: string): Promise<T | null> {
|
|
22
|
+
if (this.redis) {
|
|
23
|
+
const val = await this.redis.get(key);
|
|
24
|
+
return val ? JSON.parse(val) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cached = this.store.get(key);
|
|
28
|
+
return cached ? cached.data : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async set<T>(
|
|
32
|
+
key: string,
|
|
33
|
+
value: T,
|
|
34
|
+
tags: string[] = [],
|
|
35
|
+
ttl: number = 3600
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const entry: CacheEntry<T> = {
|
|
38
|
+
data: value,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (this.redis) {
|
|
43
|
+
await this.redis.set(key, JSON.stringify(entry.data), 'EX', ttl);
|
|
44
|
+
for (const tag of tags) {
|
|
45
|
+
await this.redis.sadd(this.redisTagKey(tag), key);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
this.store.set(key, entry);
|
|
49
|
+
for (const tag of tags) {
|
|
50
|
+
if (!this.tagsMap.has(tag)) this.tagsMap.set(tag, new Set());
|
|
51
|
+
this.tagsMap.get(tag)!.add(key);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async delete(key: string): Promise<void> {
|
|
57
|
+
if (this.redis) {
|
|
58
|
+
await this.redis.del(key);
|
|
59
|
+
|
|
60
|
+
// Also clean up from any tag sets
|
|
61
|
+
const tagKeys = await this.redis.keys('cache-tags:*');
|
|
62
|
+
for (const tagKey of tagKeys) {
|
|
63
|
+
await this.redis.srem(tagKey, key);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
this.store.delete(key);
|
|
67
|
+
for (const keys of this.tagsMap.values()) {
|
|
68
|
+
keys.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async invalidateTag(tag: string): Promise<void> {
|
|
74
|
+
if (this.redis) {
|
|
75
|
+
const tagKey = this.redisTagKey(tag);
|
|
76
|
+
const keys = await this.redis.smembers(tagKey);
|
|
77
|
+
if (keys.length) {
|
|
78
|
+
await this.redis.del(...keys); // delete all cached keys
|
|
79
|
+
await this.redis.del(tagKey); // delete the tag set
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
const keys = this.tagsMap.get(tag);
|
|
83
|
+
if (keys) {
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
this.store.delete(key);
|
|
86
|
+
}
|
|
87
|
+
this.tagsMap.delete(tag);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|