@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/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.Builder = exports.TestBuilder = exports.AvleonApplication = void 0;
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
- useCors(corsOptions = {}) {
154
- this.app.register(cors_1.default, corsOptions);
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(ConfigClass, modifyConfig) {
157
- const openApiConfig = this.appConfig.get(ConfigClass);
158
- if (modifyConfig) {
159
- const modifiedConfig = modifyConfig(openApiConfig);
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
- this.globalSwaggerOptions = openApiConfig;
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
- this.multipartOptions = options;
178
- this.app.register(multipart_1.default, {
179
- ...this.multipartOptions,
180
- attachFieldsToBody: true,
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
- * Configures and initializes a TypeORM DataSource based on the provided configuration class.
185
- * It retrieves the configuration from the application's configuration service and allows for optional modification.
186
- * The initialized DataSource is then stored and registered within a dependency injection container.
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
- this.dataSourceOptions = dsConfig;
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(dsConfig);
210
+ const datasource = new typeorm.DataSource(dataSourceOptions);
208
211
  this.dataSource = datasource;
209
212
  typedi_1.default.set("idatasource", datasource);
210
213
  }
211
- /**
212
- * Registers an array of middleware classes to be executed before request handlers.
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
- async autoControllers() {
467
- const controllers = [];
468
- const files = await promises_1.default.readdir(controllerDir);
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(controllerDir, file);
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
- this.buildController(exported);
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
- mapControllers(controllers) {
482
- this.controllers = controllers;
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
- if (!this.isMapFeatures) {
489
- this.isMapFeatures = true;
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
- mapControllersAuto() {
505
- const isExists = (0, fs_1.existsSync)(controllerDir);
506
- if (isExists) {
507
- this.autoControllers();
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 TestBuilder {
702
+ class AvleonTest {
697
703
  constructor() {
698
704
  process.env.NODE_ENV = "test";
699
705
  }
700
- static createBuilder() {
701
- if (!TestBuilder.instance) {
702
- TestBuilder.instance = new TestBuilder();
703
- }
704
- return TestBuilder.instance;
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
- getTestApplication(options) {
716
+ static createTestApplication(options) {
716
717
  const app = AvleonApplication.getInternalApp({
717
- dataSourceOptions: this.dataSourceOptions,
718
+ dataSourceOptions: options.dataSource ? options.dataSource : null,
718
719
  });
719
- app.mapControllers([...options.controllers]);
720
+ app.useControllers([...options.controllers]);
720
721
  return app.getTestApp();
721
722
  }
722
- build(app) {
723
+ static from(app) {
723
724
  return app.getTestApp();
724
725
  }
725
- fromApplication(app) {
726
- return app.getTestApp();
726
+ static clean() {
727
+ typedi_1.default.reset();
727
728
  }
728
729
  }
729
- exports.TestBuilder = TestBuilder;
730
- class Builder {
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.Builder = Builder;
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";
@@ -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
  }
@@ -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.HttpExceptions = exports.HttpResponse = void 0;
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.mapControllers(this.controllers);
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.24",
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": "rm -rf dist",
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
- "@scalar/fastify-api-reference": "*"
47
- },
48
- "peerDependenciesMeta": {
49
- "@scalar/fastify-api-reference": {
50
- "optional": true
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
  },
@@ -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
+ }
package/src/config.ts CHANGED
@@ -7,6 +7,9 @@
7
7
  import { Container, Service, Constructable } from "typedi";
8
8
  import { Environment } from "./environment-variables";
9
9
 
10
+
11
+
12
+
10
13
  export interface IConfig<T = any> {
11
14
  config(env: Environment): T;
12
15
  }