Passed
Push — develop ( fd6ace...15cef8 )
by Lorenzo
01:12
created

Cached.ts ➔ Cached   C

Complexity

Conditions 8

Size

Total Lines 72
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 8

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 72
c 0
b 0
f 0
ccs 33
cts 33
cp 1
rs 6.5733
cc 8
crap 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1 9
import { createHash } from 'node:crypto';
2
import { Cache } from '@/ExpressBeansTypes';
3 9
import { logger, registeredMethods } from '@/core';
4
import type { Request, Response } from 'express';
5 9
import { Executor } from '@/core/executor';
6
7
type BeanFunction = (...args: any) => any
8
type CacheEntry = {
9
  data: ReturnType<BeanFunction>,
10
  expiration: number,
11
}
12
13 15
const createKey = (obj: any) => createHash('sha224')
14
  .update(JSON.stringify(obj))
15
  .digest('hex');
16
17 9
const getCachedData = (cache: Map<string, CacheEntry>, key: string) => {
18 15
  const cached = cache.get(key);
19 15
  if (!cached) {
20 8
    throw new Error(`Key ${key} not found in cache`);
21
  }
22 7
  if (cached.expiration < Date.now()) {
23 1
    cache.delete(key);
24 1
    throw new Error(`Key ${key} expired in cache`);
25
  }
26 6
  return cached;
27
};
28
29 9
const isRoute = (args: unknown[]): args is [Request, Response] => {
30 15
  if (args.length !== 2) {
31 13
    return false;
32
  }
33 2
  const potentialReq = args[0] as any;
34 2
  const potentialRes = args[1] as any;
35
36 2
  return (
37
    potentialReq &&
38
    typeof potentialReq === 'object' &&
39
    'url' in potentialReq &&
40
    'method' in potentialReq &&
41
    'params' in potentialReq &&
42
    'query' in potentialReq &&
43
    potentialRes &&
44
    typeof potentialRes === 'object' &&
45
    'send' in potentialRes &&
46
    'json' in potentialRes
47
  );
48
};
49
50
/**
51
 * Caches the result of a method
52
 * @param options {Cache}
53
 * @decorator
54
 */
55 9
export function Cached<This>(
56
  options: Cache = { duration: 60_000, type: 'memory' },
57
) {
58 6
  return (
59
    method: BeanFunction,
60
    context: ClassMethodDecoratorContext<This, BeanFunction>,
61
  ) => {
62 6
    logger.debug(`Initializing cache for method ${String(context.name)}`);
63
64 6
    const cache = new Map<string, CacheEntry>();
65 6
    Executor.setExecution('init', () => {
66 16
      const bean = registeredMethods.get(method);
67 16
      bean?._interceptors.set(context.name as string, (target: any, _prop: string) => {
68
69 15
        return (...args: any[]) => {
70 15
          let keyObj: any = { args };
71 15
          if (isRoute(args)) {
72 2
            const [req, res] = args;
73 2
            keyObj = {
74
              url: req.url,
75
              method: req.method,
76
              params: req.params,
77
              query: req.query,
78
              body: req.body,
79
            };
80 2
            const key = createKey(keyObj);
81 2
            try {
82 2
              const result = getCachedData(cache, key);
83 1
              logger.debug(`Returning cached data for ${key}`);
84 1
              return res.send(result.data);
85
            } catch (error) {
86 1
              logger.debug(error);
87
88 1
              const originalSend = res.send.bind(res);
89 1
              res.send = function (body: any) {
90 1
                cache.set(key, {
91
                  data: body,
92
                  expiration: Date.now() + options.duration,
93
                });
94 1
                return originalSend(body);
95
              };
96
97 1
              return method.call(target, ...args);
98
            }
99
          } else {
100 13
            const key = createKey(keyObj);
101 13
            try {
102 13
              const cached = getCachedData(cache, key);
103 5
              logger.debug(`Cache hit for method ${String(context.name)} with key ${key}`);
104 5
              return cached.data;
105
            } catch {
106 8
              logger.debug(`Cache miss for method ${String(context.name)} with key ${key}`);
107 8
              const result = method.apply(target, args);
108 8
              cache.set(key, {
109
                data: result,
110
                expiration: Date.now() + options.duration,
111
              });
112 8
              return result;
113
            }
114
          }
115
        };
116
117
118
      });
119
    });
120 6
    return method;
121
  };
122
}
123