exframe-cache-manager 2.1.3 → 2.2.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/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM node:16-alpine
1
+ FROM node:24-alpine
2
2
 
3
3
  LABEL maintainer=Exzeo
4
4
 
@@ -10,8 +10,10 @@ COPY ./package.json /app/package.json
10
10
  COPY . /app
11
11
 
12
12
  WORKDIR /app
13
- RUN npm install
13
+ RUN npm install --ignore-scripts
14
14
  RUN npm run peers
15
15
 
16
+ USER node
17
+
16
18
  # Override the command, to run the test instead of the application
17
19
  CMD ["npm", "run", "unit-test"]
@@ -1,4 +1,3 @@
1
- version: "2"
2
1
  services:
3
2
  exframe-cache-manager:
4
3
  container_name: exframe-cache-manager
package/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const util = require('util');
4
- const { setTimeout: waitTimeout } = require('timers/promises');
5
- const CacheManager = require('cache-manager');
6
- const RedisStore = require('cache-manager-redis-store');
7
- const { lazyInstrument } = require('exframe-metrics');
8
- const health = require('exframe-health');
9
- const service = require('exframe-service');
10
- const { generateShortId } = require('exframe-utilities');
3
+ import util from 'node:util';
4
+ import { setTimeout as waitTimeout } from 'node:timers/promises';
5
+ import CacheManager from 'cache-manager';
6
+ import RedisStore from 'cache-manager-redis-store';
7
+ import { lazyInstrument } from 'exframe-metrics';
8
+ import health from 'exframe-health';
9
+ import service from 'exframe-service';
10
+ import { generateShortId } from 'exframe-utilities';
11
+
12
+ const DEFAULT_TTL = 600;
11
13
 
12
14
  const db = 0;
13
15
 
@@ -24,7 +26,7 @@ function cachemanager(options) {
24
26
 
25
27
  const redisCache = CacheManager.caching({
26
28
  store: RedisStore,
27
- ttl: 600,
29
+ ttl: DEFAULT_TTL,
28
30
  db,
29
31
  ...options
30
32
  });
@@ -98,6 +100,30 @@ function cachemanager(options) {
98
100
  return value;
99
101
  },
100
102
 
103
+ /**
104
+ * Set the item if it does not exist yet. If the item already exists, do not set and return null.
105
+ * @template T
106
+ * @param {String} key
107
+ * @param {T} value
108
+ * @param {Number} ttl
109
+ * @returns {Promise<T|null>}
110
+ */
111
+ async setItemIfNotExists(key, value, ttl) {
112
+ const ttlToSet = ttl ?? (process.env.REDIS_CACHE_TTL
113
+ ? Number(process.env.REDIS_CACHE_TTL)
114
+ : DEFAULT_TTL);
115
+
116
+ return new Promise((resolve, reject) => {
117
+ redisClient.set(key, value, 'NX', 'EX', ttlToSet, (error, result) => {
118
+ if (error) {
119
+ return reject(error);
120
+ }
121
+
122
+ resolve(result === 'OK' ? value : null);
123
+ });
124
+ });
125
+ },
126
+
101
127
  /**
102
128
  *
103
129
  * @param {string} key
@@ -226,9 +252,8 @@ function cachemanager(options) {
226
252
  return instance;
227
253
  }
228
254
 
229
- module.exports = {
230
- create: cachemanager
231
- };
255
+ // eslint-disable-next-line import/prefer-default-export
256
+ export { cachemanager as create };
232
257
 
233
258
  /**
234
259
  * @typedef {RedisCacheManagerOptions & { store?: any, ttl?: number, db?: number }} Options
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "exframe-cache-manager",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "description": "Managing the cache",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "config": {
7
8
  "reporter": "mocha-exzeo-reporter"
8
9
  },
@@ -24,14 +25,14 @@
24
25
  "exframe-service": "1.x"
25
26
  },
26
27
  "dependencies": {
27
- "@types/cache-manager": "^3.4.2",
28
+ "@types/cache-manager": "^4.0.6",
28
29
  "cache-manager": "^3.6.0",
29
30
  "cache-manager-redis-store": "^2.0.0",
30
- "exframe-metrics": "^1.1.0",
31
+ "exframe-metrics": "^1.5.0",
31
32
  "exframe-utilities": "^1.1.0"
32
33
  },
33
34
  "devDependencies": {
34
- "chai": "^4",
35
+ "chai": "^6",
35
36
  "eslint": "*",
36
37
  "eslint-config-exzeo": "*",
37
38
  "eslint-plugin-import": "*",
@@ -46,5 +47,5 @@
46
47
  "url": "https://bitbucket.org/exzeo-usa/exframe",
47
48
  "directory": "packages/exframe-cache-manager"
48
49
  },
49
- "gitHead": "fc8904cc6138fcd0976e4cc3692134e84dcaa985"
50
+ "gitHead": "25463e1a2459e770277640472111e372cacf4aa5"
50
51
  }
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
- const service = require('exframe-service');
6
- const health = require('exframe-health');
7
- const { setTimeout: waitTimeout } = require('timers/promises');
8
- const cacheManager = require('../index');
3
+ import { expect } from 'chai';
4
+ import { createSandbox, match, stub as _stub } from 'sinon';
5
+ import service from 'exframe-service';
6
+ import health from 'exframe-health';
7
+ import { setTimeout as waitTimeout } from 'node:timers/promises';
8
+
9
+ import { create } from '../index.js';
9
10
 
10
11
  const { prometheusClient } = service;
11
12
  const userId = 'auth0|1234567890';
@@ -25,7 +26,7 @@ context('Test User Profile Middleware', () => {
25
26
  let sinonInstance;
26
27
 
27
28
  beforeEach(() => {
28
- sinonInstance = sinon.createSandbox();
29
+ sinonInstance = createSandbox();
29
30
  });
30
31
 
31
32
  afterEach(() => {
@@ -35,25 +36,25 @@ context('Test User Profile Middleware', () => {
35
36
  it('adds a health check with the instance ID in the name, check function, and liveness timeout of 60 seconds', () => {
36
37
  const healthAddSpy = sinonInstance.spy(health, 'add');
37
38
 
38
- const instance = cacheManager.create(options);
39
+ const instance = create(options);
39
40
  expect(healthAddSpy.calledOnce).to.equal(true);
40
- expect(healthAddSpy.calledWithMatch(`redis-${instance.id}`, sinon.match.func, { promotionTimeout: 60000 })).to.be.true;
41
+ expect(healthAddSpy.calledWithMatch(`redis-${instance.id}`, match.func, { promotionTimeout: 60000 })).to.be.true;
41
42
  });
42
43
 
43
44
  it('registers a service resource with the instance ID in the name, onSignal function, and order of "last"', () => {
44
45
  const registerSpy = sinonInstance.spy(service, 'registerResource');
45
46
 
46
- const instance = cacheManager.create(options);
47
+ const instance = create(options);
47
48
  expect(registerSpy.calledOnce).to.equal(true);
48
- expect(registerSpy.calledWithMatch(`exframe-cache-manager-${instance.id}`, sinon.match({
49
- onSignal: sinon.match.func,
49
+ expect(registerSpy.calledWithMatch(`exframe-cache-manager-${instance.id}`, match({
50
+ onSignal: match.func,
50
51
  order: 'last'
51
52
  }))).to.be.true;
52
53
  });
53
54
  });
54
55
 
55
56
  describe('instance', () => {
56
- const app = cacheManager.create(options);
57
+ const app = create(options);
57
58
 
58
59
  beforeEach(() => {
59
60
  prometheusClient.register.clear();
@@ -69,6 +70,39 @@ context('Test User Profile Middleware', () => {
69
70
  await app.flushCache({ log: { info: (info) => { console.log(info); } } }, cacheKey);
70
71
  });
71
72
 
73
+ it('validates the setItemIfNotExists function on the basis of key and value', async () => {
74
+ const data = await app.setItemIfNotExists(cacheKey, 'value1');
75
+ expect(data).to.be.ok.and.to.eql('value1');
76
+
77
+ const metrics = prometheusClient.register.getMetricsAsArray().filter(({ name }) => name.includes('setItemIfNotExists'));
78
+ expect(metrics).to.be.an('array').and.to.have.lengthOf(1);
79
+
80
+ await app.flushCache({ log: { info: (info) => { console.log(info); } } }, cacheKey);
81
+ });
82
+
83
+ it('validates the setItemIfNotExists function on the basis of key, value, and ttl', async () => {
84
+ const data = await app.setItemIfNotExists(cacheKey, 'value1', 1000);
85
+ expect(data).to.be.ok.and.to.eql('value1');
86
+
87
+ const metrics = prometheusClient.register.getMetricsAsArray().filter(({ name }) => name.includes('setItemIfNotExists'));
88
+ expect(metrics).to.be.an('array').and.to.have.lengthOf(1);
89
+
90
+ await app.flushCache({ log: { info: (info) => { console.log(info); } } }, cacheKey);
91
+ });
92
+
93
+ it('validates the setItemIfNotExists function returns null if key is already set', async () => {
94
+ const data1 = await app.setItemIfNotExists(cacheKey, 'value1', 1000);
95
+ expect(data1).to.be.ok.and.to.eql('value1');
96
+
97
+ const data2 = await app.setItemIfNotExists(cacheKey, 'value2');
98
+ expect(data2).to.eql(null);
99
+
100
+ const metrics = prometheusClient.register.getMetricsAsArray().filter(({ name }) => name.includes('setItemIfNotExists'));
101
+ expect(metrics).to.be.an('array').and.to.have.lengthOf(1);
102
+
103
+ await app.flushCache({ log: { info: (info) => { console.log(info); } } }, cacheKey);
104
+ });
105
+
72
106
  it('validate the getItem function on the basis of key', async () => {
73
107
  await app.setItem(cacheKey, 'someValue');
74
108
  const value = await app.getItem(cacheKey);
@@ -94,7 +128,7 @@ context('Test User Profile Middleware', () => {
94
128
  await waitTimeout(5000);
95
129
  return 'finished';
96
130
  };
97
- const stub = sinon.stub(app.redisCache, 'get').callsFake(fake);
131
+ const stub = _stub(app.redisCache, 'get').callsFake(fake);
98
132
 
99
133
  await app.setItem(cacheKey, 'someValue');
100
134
  const context = { log: { info: (info) => { console.log(info); } } };
@@ -144,7 +178,7 @@ context('Test User Profile Middleware', () => {
144
178
  });
145
179
 
146
180
  it('close drains the redis pool', async () => {
147
- const app1 = cacheManager.create(options);
181
+ const app1 = create(options);
148
182
  await app1.setItem(cacheKey, 'someValue');
149
183
  const value = await app1.getItem(cacheKey);
150
184
  expect(value).to.eql('someValue');