@boredland/node-ts-cache 5.0.1 → 6.0.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
@@ -1,14 +1,15 @@
1
- # boredland/node-ts-cache
1
+ # @boredland/node-ts-cache
2
2
 
3
3
  [![CI](https://github.com/boredland/node-ts-cache/actions/workflows/ci.yml/badge.svg)](https://github.com/boredland/node-ts-cache/actions/workflows/ci.yml)
4
4
  [![The MIT License](https://img.shields.io/npm/l/node-ts-cache.svg)](http://opensource.org/licenses/MIT)
5
+ [![Coverage Status](https://coveralls.io/repos/github/boredland/node-ts-cache/badge.svg?branch=main)](https://coveralls.io/github/boredland/node-ts-cache?branch=main)
5
6
 
6
7
  Simple and extensible caching module supporting decorators.
7
8
 
8
9
  ## Install
9
10
 
10
11
  ```bash
11
- yarn add boredland/node-ts-cache
12
+ yarn add @boredland/node-ts-cache
12
13
  ```
13
14
 
14
15
  _Note: The underlying storage layer must be installed separately._
@@ -17,9 +18,10 @@ _Note: The underlying storage layer must be installed separately._
17
18
 
18
19
  | Storage | Install |
19
20
  |-----------------------------------------------------------------------|-------------------------------------------------|
20
- | [memory](https://www.npmjs.com/package/boredland/node-ts-cache-storage-memory)| ```yarn add @boredland/node-ts-cache-storage-memory```|
21
- | [node-fs](https://www.npmjs.com/package/boredland/node-ts-cache-storage-node-fs)| ```yarn add @boredland/node-ts-cache-storage-node-fs```|
22
- | [ioredis](https://www.npmjs.com/package/boredland/node-ts-cache-storage-ioredis)| ```yarn add @boredland/node-ts-cache-storage-ioredis```|
21
+ | [memory](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-memory)| ```yarn add @boredland/node-ts-cache-storage-memory```|
22
+ | [node-fs](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-node-fs)| ```yarn add @boredland/node-ts-cache-storage-node-fs```|
23
+ | [ioredis](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-ioredis)| ```yarn add @boredland/node-ts-cache-storage-ioredis```|
24
+ | [postgres](https://www.npmjs.com/package/@boredland/node-ts-cache-storage-pg)| ```yarn add @boredland/node-ts-cache-storage-pg```|
23
25
 
24
26
  ## Usage
25
27
 
@@ -70,7 +72,7 @@ const myCache = new CacheContainer(new MemoryStorage())
70
72
 
71
73
  class MyService {
72
74
  public async getUsers(): Promise<string[]> {
73
- const cachedUsers = await myCache.getItem<string[]>("users")
75
+ const { content: cachedUsers } = await myCache.getItem<string[]>("users")
74
76
 
75
77
  if (cachedUsers) {
76
78
  return cachedUsers
@@ -2,14 +2,14 @@ export declare type CachedItem<T = any> = {
2
2
  content: T;
3
3
  meta: {
4
4
  createdAt: number;
5
- ttl: number;
5
+ ttl: number | null;
6
6
  isLazy: boolean;
7
7
  };
8
8
  };
9
9
  export declare type CachingOptions = {
10
10
  /** (Default: 60) Number of seconds to expire the cachte item */
11
11
  ttl: number;
12
- /** (Default: true) If true, expired cache entries will be deleted on touch. If false, entries will be deleted after the given ttl. */
12
+ /** (Default: true) If true, expired cache entries will be deleted on touch and returned anyway. If false, entries will be deleted after the given ttl. */
13
13
  isLazy: boolean;
14
14
  /** (Default: false) If true, cache entry has no expiration. */
15
15
  isCachedForever: boolean;
@@ -1,11 +1,17 @@
1
- import type { IStorage } from "../storage";
1
+ import type { Storage } from "../storage";
2
2
  import type { CachingOptions } from "./cache-container-types";
3
3
  export declare class CacheContainer {
4
4
  private storage;
5
- constructor(storage: IStorage);
6
- getItem<T>(key: string): Promise<T | undefined>;
7
- setItem(key: string, content: any, options: Partial<CachingOptions>): Promise<void>;
5
+ constructor(storage: Storage);
6
+ getItem<T>(key: string): Promise<{
7
+ content: T;
8
+ meta: {
9
+ expired: boolean;
10
+ createdAt: number;
11
+ };
12
+ } | undefined>;
13
+ setItem(key: string, content: unknown, options?: Partial<CachingOptions>): Promise<void>;
8
14
  clear(): Promise<void>;
9
15
  private isItemExpired;
10
- private unsetKey;
16
+ unsetKey(key: string): Promise<void>;
11
17
  }
@@ -14,30 +14,33 @@ class CacheContainer {
14
14
  }
15
15
  async getItem(key) {
16
16
  const item = await this.storage.getItem(key);
17
- if (item?.meta?.ttl && this.isItemExpired(item)) {
17
+ if (!item)
18
+ return;
19
+ const result = {
20
+ content: item.content,
21
+ meta: {
22
+ createdAt: item.meta.createdAt,
23
+ expired: this.isItemExpired(item),
24
+ },
25
+ };
26
+ if (result.meta.expired)
18
27
  await this.unsetKey(key);
28
+ if (result.meta.expired && !item.meta.isLazy)
19
29
  return undefined;
20
- }
21
- return item ? item.content : undefined;
30
+ return result;
22
31
  }
23
32
  async setItem(key, content, options) {
24
33
  const finalOptions = {
25
34
  ttl: DEFAULT_TTL_SECONDS,
26
35
  isLazy: true,
27
36
  isCachedForever: false,
28
- ...options
37
+ ...options,
29
38
  };
30
39
  const meta = {
31
40
  createdAt: Date.now(),
32
41
  isLazy: finalOptions.isLazy,
33
- ttl: finalOptions.isCachedForever ? Infinity : finalOptions.ttl * 1000
42
+ ttl: finalOptions.isCachedForever ? null : finalOptions.ttl * 1000,
34
43
  };
35
- if (!finalOptions.isCachedForever && !finalOptions.isLazy) {
36
- setTimeout(() => {
37
- this.unsetKey(key);
38
- debug(`Expired key ${key} removed from cache`);
39
- }, meta.ttl);
40
- }
41
44
  await this.storage.setItem(key, { meta, content });
42
45
  }
43
46
  async clear() {
@@ -45,12 +48,12 @@ class CacheContainer {
45
48
  debug("Cleared cache");
46
49
  }
47
50
  isItemExpired(item) {
48
- if (item.meta.ttl === Infinity)
51
+ if (item.meta.ttl === null)
49
52
  return false;
50
53
  return Date.now() > item.meta.createdAt + item.meta.ttl;
51
54
  }
52
55
  async unsetKey(key) {
53
- await this.storage.setItem(key, undefined);
56
+ await this.storage.removeItem(key);
54
57
  }
55
58
  }
56
59
  exports.CacheContainer = CacheContainer;
@@ -10,7 +10,9 @@ const jsonCalculateKey = (data) => {
10
10
  return `${data.className}:${data.methodName}:${JSON.stringify(data.args)}`;
11
11
  };
12
12
  function Cache(container, options) {
13
- return function (target, methodName, descriptor) {
13
+ return function (target, methodName,
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ descriptor) {
14
16
  const originalMethod = descriptor.value;
15
17
  const className = target.constructor.name;
16
18
  descriptor.value = async function (...args) {
@@ -18,21 +20,14 @@ function Cache(container, options) {
18
20
  const keyOptions = {
19
21
  args,
20
22
  methodName: methodName,
21
- className
23
+ className,
22
24
  };
23
25
  const cacheKey = options.calculateKey
24
26
  ? options.calculateKey(keyOptions)
25
27
  : jsonCalculateKey(keyOptions);
26
- const runOriginalMethod = async () => {
28
+ const runOriginalMethod = () => {
27
29
  const methodCall = originalMethod.apply(this, args);
28
- const isAsync = methodCall?.constructor?.name === "AsyncFunction" ||
29
- methodCall?.constructor?.name === "Promise";
30
- if (isAsync) {
31
- return await methodCall;
32
- }
33
- else {
34
- return methodCall;
35
- }
30
+ return methodCall;
36
31
  };
37
32
  if (!target.__node_ts_cache_method_run_queue) {
38
33
  target.__node_ts_cache_method_run_queue = {};
@@ -46,7 +41,7 @@ function Cache(container, options) {
46
41
  const entry = await container.getItem(cacheKey);
47
42
  if (entry) {
48
43
  debug(`Cache HIT ${cacheKey}`);
49
- return entry;
44
+ return entry.content;
50
45
  }
51
46
  debug(`Cache MISS ${cacheKey}`);
52
47
  const methodResult = await runOriginalMethod();
@@ -54,8 +49,8 @@ function Cache(container, options) {
54
49
  return methodResult;
55
50
  }
56
51
  finally {
57
- target.__node_ts_cache_method_run_queue[cacheKey] =
58
- undefined;
52
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
53
+ target.__node_ts_cache_method_run_queue[cacheKey] = undefined;
59
54
  }
60
55
  })();
61
56
  return target.__node_ts_cache_method_run_queue[cacheKey];
@@ -1,6 +1,7 @@
1
1
  import type { CachedItem } from "../cache-container";
2
- export interface IStorage {
2
+ export interface Storage {
3
3
  getItem(key: string): Promise<CachedItem | undefined>;
4
- setItem(key: string, content: CachedItem | undefined): Promise<void>;
4
+ setItem(key: string, content: CachedItem): Promise<void>;
5
+ removeItem(key: string): Promise<void>;
5
6
  clear(): Promise<void>;
6
7
  }
@@ -1,9 +1,11 @@
1
1
  import type { CacheContainer, CachingOptions } from "./cache-container";
2
- declare type WithCacheOptions<Parameters> = Partial<Omit<CachingOptions, 'calculateKey'>> & {
2
+ declare type WithCacheOptions<Parameters> = Partial<Omit<CachingOptions, "calculateKey">> & {
3
3
  /** an optional prefix to prepend to the key */
4
4
  prefix?: string;
5
5
  /** an optional function to calculate a key based on the parameters of the wrapped function */
6
6
  calculateKey?: (input: Parameters) => string;
7
+ /** an optional function that is called when a lazy item has expired and thus got removed */
8
+ afterExpired?: () => Promise<void>;
7
9
  };
8
10
  /**
9
11
  * wrapped function factory
package/dist/withCache.js CHANGED
@@ -15,12 +15,15 @@ const withCacheFactory = (container) => {
15
15
  */
16
16
  const withCache = (operation, options = {}) => {
17
17
  return async (...parameters) => {
18
- let { prefix, calculateKey, ...rest } = options;
19
- prefix = prefix ?? 'default';
18
+ const { calculateKey, ...rest } = options;
19
+ const prefix = options.prefix ?? "default";
20
20
  const key = `${operation.name}:${prefix}:${calculateKey ? calculateKey(parameters) : JSON.stringify(parameters)}`;
21
21
  const cachedResponse = await container.getItem(key);
22
22
  if (cachedResponse) {
23
- return cachedResponse;
23
+ if (cachedResponse.meta.expired && options.afterExpired) {
24
+ await options.afterExpired();
25
+ }
26
+ return cachedResponse.content;
24
27
  }
25
28
  const result = await operation(...parameters);
26
29
  await container.setItem(key, result, rest);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@boredland/node-ts-cache",
3
3
  "description": "Simple and extensible caching module supporting decorators",
4
- "version": "5.0.1",
4
+ "version": "6.0.0",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",