@ehildt/nestjs-config-factory 1.0.0 → 1.1.0

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 CHANGED
@@ -18,9 +18,9 @@ A NestJS configuration library providing decorators and modules for caching retu
18
18
 
19
19
  # StatusBadges
20
20
 
21
- ![github](https://img.shields.io/github/release/ehildt/nestjs--config--factory?labelColor=333&style=for-the-badge&cacheSeconds=3600&color=b16425&logo=github&logoColor=b16425&logoWidth=40&branch=main)
22
- ![github](https://img.shields.io/github/stars/ehildt/nestjs--config--factory?labelColor=333&style=for-the-badge&cacheSeconds=3600&color=b16425&logo=github&logoColor=b16425&logoWidth=40&branch=main)
23
- ![github](https://img.shields.io/github/license/ehildt/nestjs--config--factory?labelColor=333&style=for-the-badge&cacheSeconds=3600&color=b16425&logo=github&logoColor=b16425&logoWidth=40&branch=main)
21
+ ![github](https://img.shields.io/github/release/ehildt/nestjs-config-factory?labelColor=333&style=for-the-badge&cacheSeconds=3600&color=b16425&logo=github&logoColor=b16425&logoWidth=40&branch=main)
22
+ ![github](https://img.shields.io/github/stars/ehildt/nestjs-config-factory?labelColor=333&style=for-the-badge&cacheSeconds=3600&color=b16425&logo=github&logoColor=b16425&logoWidth=40&branch=main)
23
+ ![github](https://img.shields.io/github/license/ehildt/nestjs-config-factory?labelColor=333&style=for-the-badge&cacheSeconds=3600&color=b16425&logo=github&logoColor=b16425&logoWidth=40&branch=main)
24
24
 
25
25
  </div>
26
26
 
@@ -1,5 +1,9 @@
1
1
  import Joi from 'joi';
2
2
 
3
- declare function CacheReturnValue<T = unknown>(schema?: Joi.Schema<T>): MethodDecorator & PropertyDecorator;
3
+ interface CacheConfig<T = unknown> {
4
+ schema?: Joi.Schema<T>;
5
+ ttl?: number | false;
6
+ }
7
+ declare function CacheReturnValue<T = unknown>(config?: Joi.Schema<T> | CacheConfig<T> | false): MethodDecorator & PropertyDecorator;
4
8
 
5
- export { CacheReturnValue };
9
+ export { type CacheConfig, CacheReturnValue };
@@ -12,24 +12,69 @@ var ValidateReturnValueError = class extends Error {
12
12
  };
13
13
 
14
14
  // src/cache-return-value/cache-return-value.decorator.ts
15
- function CacheReturnValue(schema) {
15
+ var DEFAULT_TTL_MS = 5 * 60 * 1e3;
16
+ function isJoiSchema(value) {
17
+ return typeof value === "object" && value !== null && "type" in value && typeof value.validate === "function";
18
+ }
19
+ function parseConfig(config) {
20
+ if (config === false) return { ttl: false };
21
+ if (config === void 0) return { ttl: DEFAULT_TTL_MS };
22
+ if (typeof config === "number") return { ttl: config };
23
+ if (isJoiSchema(config)) return { schema: config, ttl: DEFAULT_TTL_MS };
24
+ return {
25
+ schema: config.schema,
26
+ ttl: config.ttl ?? DEFAULT_TTL_MS
27
+ };
28
+ }
29
+ function validateValue(value, schema) {
30
+ if (schema) {
31
+ const { error, value: validated } = schema.validate(value, {
32
+ abortEarly: false
33
+ });
34
+ if (error) throw new ValidateReturnValueError(`Schema violation`, error.details);
35
+ return validated;
36
+ }
37
+ return value;
38
+ }
39
+ function isExpired(entry, ttl) {
40
+ if (ttl === false) return false;
41
+ return Date.now() - entry.timestamp > ttl;
42
+ }
43
+ function CacheReturnValue(config) {
16
44
  return function(_target, propertyKey, descriptor) {
45
+ const { schema, ttl } = parseConfig(config);
46
+ const setCache = (instance, key, value, refreshPromise) => {
47
+ let instanceCache = INSTANCE_CACHE.get(instance);
48
+ if (!instanceCache) {
49
+ instanceCache = /* @__PURE__ */ new Map();
50
+ INSTANCE_CACHE.set(instance, instanceCache);
51
+ }
52
+ instanceCache.set(key, { value, timestamp: Date.now(), refreshPromise });
53
+ };
17
54
  if (descriptor?.get) {
18
55
  const originalGet = descriptor.get;
19
56
  descriptor.get = function() {
20
57
  const cache = getCacheFor(this);
21
- if (!cache.has(String(propertyKey))) {
22
- let value = originalGet.call(this);
23
- if (schema) {
24
- const { error, value: validated } = schema.validate(value, {
25
- abortEarly: false
26
- });
27
- if (error) throw new ValidateReturnValueError(`Schema violation`, error.details);
28
- value = validated;
29
- }
30
- cache.set(String(propertyKey), value);
58
+ const key = String(propertyKey);
59
+ const entry = cache.get(key);
60
+ if (!entry) {
61
+ const value = validateValue(originalGet.call(this), schema);
62
+ setCache(this, key, value);
63
+ return value;
31
64
  }
32
- return cache.get(String(propertyKey));
65
+ if (!isExpired(entry, ttl)) return entry.value;
66
+ const staleValue = entry.value;
67
+ if (entry.refreshPromise) return staleValue;
68
+ const refreshPromise = (async () => {
69
+ return validateValue(await originalGet.call(this), schema);
70
+ })();
71
+ setCache(this, key, staleValue, refreshPromise);
72
+ refreshPromise.then((freshValue) => {
73
+ setCache(this, key, freshValue);
74
+ }).catch(() => {
75
+ setCache(this, key, staleValue);
76
+ });
77
+ return staleValue;
33
78
  };
34
79
  }
35
80
  if (descriptor?.value instanceof Function) {
@@ -37,18 +82,25 @@ function CacheReturnValue(schema) {
37
82
  descriptor.value = function(...args) {
38
83
  const cache = getCacheFor(this);
39
84
  const key = hashPayload(`${String(propertyKey)}:${JSON.stringify(args)}`);
40
- if (!cache.has(key)) {
41
- let result = originalMethod.apply(this, args);
42
- if (schema) {
43
- const { error, value: validated } = schema.validate(result, {
44
- abortEarly: false
45
- });
46
- if (error) throw new ValidateReturnValueError(`Schema violation`, error.details);
47
- result = validated;
48
- }
49
- cache.set(key, result);
85
+ const entry = cache.get(key);
86
+ if (!entry) {
87
+ const result = validateValue(originalMethod.apply(this, args), schema);
88
+ setCache(this, key, result);
89
+ return result;
50
90
  }
51
- return cache.get(key);
91
+ if (!isExpired(entry, ttl)) return entry.value;
92
+ const staleValue = entry.value;
93
+ if (entry.refreshPromise) return staleValue;
94
+ const refreshPromise = (async () => {
95
+ return validateValue(await originalMethod.apply(this, args), schema);
96
+ })();
97
+ setCache(this, key, staleValue, refreshPromise);
98
+ refreshPromise.then((freshValue) => {
99
+ setCache(this, key, freshValue);
100
+ }).catch(() => {
101
+ setCache(this, key, staleValue);
102
+ });
103
+ return staleValue;
52
104
  };
53
105
  }
54
106
  };
@@ -9,6 +9,20 @@ var __decorateClass = (decorators, target, key, kind) => {
9
9
  return result;
10
10
  };
11
11
  var ConfigFactoryModule = class {
12
+ /**
13
+ * Creates a dynamic module with the specified configuration providers.
14
+ * All providers are automatically exported for use by other modules.
15
+ *
16
+ * @param options - Configuration options
17
+ * @param options.global - Whether to register as a global module
18
+ * @param options.providers - Array of configuration provider classes
19
+ *
20
+ * @example
21
+ * ConfigFactoryModule.forRoot({
22
+ * global: true,
23
+ * providers: [DatabaseConfigService, AppConfigService],
24
+ * });
25
+ */
12
26
  static forRoot({ global = false, providers = [] } = {}) {
13
27
  if (!providers.length)
14
28
  console.warn("[ConfigFactoryModule] No providers registered.");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ehildt/nestjs-config-factory",
3
3
  "description": "A dynamic NestJS module factory designed to register, manage, and expose custom configuration providers throughout the application.",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {