@k03mad/ip2geo 4.2.1 → 5.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.
@@ -25,6 +25,9 @@ jobs:
25
25
  with:
26
26
  node-version-file: '.nvmrc'
27
27
 
28
+ - name: Install PNPM
29
+ run: npm i pnpm -g
30
+
28
31
  - name: Run setup
29
32
  run: npm run setup
30
33
 
@@ -25,6 +25,9 @@ jobs:
25
25
  with:
26
26
  node-version-file: '.nvmrc'
27
27
 
28
+ - name: Install PNPM
29
+ run: npm i pnpm -g
30
+
28
31
  - name: Run setup
29
32
  run: npm run setup
30
33
 
package/README.md CHANGED
@@ -45,7 +45,8 @@ const info = await ip2geo('1.1.1.1', {
45
45
  cacheFileSeparator: ';;',
46
46
  cacheFileNewline: '\n',
47
47
  cacheMap: new Map(),
48
- rps: 3, // useful in Promise.all with IPs array
48
+ cacheMapMaxEntries: Number.POSITIVE_INFINITY, // store last N requests, 0 — turns cache map off
49
+ rps: 3, // API RPS, useful in Promise.all with IPs array
49
50
  });
50
51
 
51
52
  // info {
package/app/lib/ip2geo.js CHANGED
@@ -26,6 +26,7 @@ const DEFAULT_CACHE_FILE_DIR = path.join(os.tmpdir(), '.ip2geo');
26
26
  const DEFAULT_CACHE_FILE_NAME = 'ips.log';
27
27
  const DEFAULT_CACHE_FILE_SEPARATOR = ';;';
28
28
  const DEFAULT_CACHE_FILE_NEWLINE = '\n';
29
+ const DEFAULT_CACHE_MAP_MAX_ENTRIES = Number.POSITIVE_INFINITY;
29
30
  const DEFAULT_RPS = 3;
30
31
 
31
32
  export const cacheStorage = new Map();
@@ -111,8 +112,9 @@ const writeToFsCache = async (ip, data, cacheDir, cacheFileName, cacheFileSepara
111
112
  * @param {string} [opts.cacheFileName]
112
113
  * @param {string} [opts.cacheFileSeparator]
113
114
  * @param {string} [opts.cacheFileNewline]
114
- * @param {Map} [opts.cacheMap]
115
- * @param {number} opts.rps
115
+ * @param {Map|false} [opts.cacheMap]
116
+ * @param {number} [opts.cacheMapMaxEntries]
117
+ * @param {number} [opts.rps]
116
118
  * @returns {Promise<GeoIpOutput>}
117
119
  */
118
120
  export const ip2geo = async (ip = '', {
@@ -120,15 +122,18 @@ export const ip2geo = async (ip = '', {
120
122
  cacheFileName = DEFAULT_CACHE_FILE_NAME,
121
123
  cacheFileSeparator = DEFAULT_CACHE_FILE_SEPARATOR,
122
124
  cacheFileNewline = DEFAULT_CACHE_FILE_NEWLINE,
125
+ cacheMapMaxEntries = DEFAULT_CACHE_MAP_MAX_ENTRIES,
123
126
  cacheMap = cacheStorage,
124
127
  rps = DEFAULT_RPS,
125
128
  } = {}) => {
126
129
  if (ip) {
127
- const ipData = cacheMap.get(ip);
130
+ if (cacheMapMaxEntries > 0) {
131
+ const ipData = cacheMap.get(ip);
128
132
 
129
- if (ipData) {
130
- debug('get from map cache: %o', ipData);
131
- return ipData;
133
+ if (ipData) {
134
+ debug('get from map cache: %o', ipData);
135
+ return ipData;
136
+ }
132
137
  }
133
138
 
134
139
  const fsCache = await readFromFsCache(ip, cacheDir, cacheFileName);
@@ -143,7 +148,11 @@ export const ip2geo = async (ip = '', {
143
148
  const outputData = collectOutputData(fileData);
144
149
  debug('get from fs cache: %o', outputData);
145
150
 
146
- cacheMap.set(ip, outputData);
151
+ if (cacheMapMaxEntries > 0) {
152
+ cacheMap.set(ip, outputData);
153
+ debug('set to map cache: %o', outputData);
154
+ }
155
+
147
156
  return outputData;
148
157
  }
149
158
  }
@@ -170,8 +179,10 @@ export const ip2geo = async (ip = '', {
170
179
 
171
180
  const outputData = collectOutputData(usedData);
172
181
 
173
- cacheMap.set(body.ip, outputData);
174
- debug('set to map cache: %o', outputData);
182
+ if (cacheMapMaxEntries > 0) {
183
+ cacheMap.set(body.ip, outputData);
184
+ debug('set to map cache: %o', outputData);
185
+ }
175
186
 
176
187
  await writeToFsCache(
177
188
  body.ip, usedData,
@@ -179,5 +190,19 @@ export const ip2geo = async (ip = '', {
179
190
  );
180
191
 
181
192
  debug('set to fs cache: %o', outputData);
193
+
194
+ if (cacheMap.size > cacheMapMaxEntries) {
195
+ debug('remove from map cache by limit: size %o, limit: %o', cacheMap.size, cacheMapMaxEntries);
196
+
197
+ for (const [key] of cacheMap) {
198
+ debug('remove from map cache by limit: key %o', key);
199
+ cacheMap.delete(key);
200
+
201
+ if (cacheMap.size <= cacheMapMaxEntries) {
202
+ break;
203
+ }
204
+ }
205
+ }
206
+
182
207
  return outputData;
183
208
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k03mad/ip2geo",
3
- "version": "4.2.1",
3
+ "version": "5.0.0",
4
4
  "description": "GeoIP library",
5
5
  "maintainers": [
6
6
  "Kirill Molchanov <k03.mad@gmail.com"
@@ -25,7 +25,7 @@
25
25
  "debug": "4.3.4"
26
26
  },
27
27
  "devDependencies": {
28
- "@k03mad/eslint-config": "19.4.0",
28
+ "@k03mad/eslint-config": "19.5.0",
29
29
  "eslint": "8.56.0",
30
30
  "husky": "8.0.3",
31
31
  "mocha": "10.2.0"
@@ -37,7 +37,7 @@
37
37
  "clean": "npm run clean:modules && npm run clean:eslint:cache",
38
38
  "clean:modules": "rm -rf ./node_modules || true",
39
39
  "clean:eslint:cache": "rm -rf .eslintcache || true",
40
- "setup": "npm run clean && npm i",
40
+ "setup": "npm run clean && pnpm i",
41
41
  "prepare": "husky install || true"
42
42
  }
43
43
  }
@@ -11,11 +11,9 @@ import {removeCacheFolder} from './shared/fs.js';
11
11
  const testName = getCurrentFilename(import.meta.url);
12
12
 
13
13
  describe(testName, () => {
14
- const cacheMap = new Map();
15
-
16
14
  const opts = {
17
15
  cacheDir: getTestFolder(testName),
18
- cacheMap,
16
+ cacheMap: new Map(),
19
17
  };
20
18
 
21
19
  it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
@@ -26,8 +24,8 @@ describe(testName, () => {
26
24
  });
27
25
 
28
26
  it('should have 1 correct cache entry', () => {
29
- assert.equal(cacheMap.size, 1);
30
- assert.deepEqual(cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
27
+ assert.equal(opts.cacheMap.size, 1);
28
+ assert.deepEqual(opts.cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
31
29
  });
32
30
 
33
31
  it(`should return correct response for IP: "${REQUEST_IPV6.ip}"`, async () => {
@@ -36,8 +34,8 @@ describe(testName, () => {
36
34
  });
37
35
 
38
36
  it('should have 2 correct cache entries', () => {
39
- assert.equal(cacheMap.size, 2);
40
- assert.deepEqual(cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
41
- assert.deepEqual(cacheMap.get(REQUEST_IPV6.ip), REQUEST_IPV6);
37
+ assert.equal(opts.cacheMap.size, 2);
38
+ assert.deepEqual(opts.cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
39
+ assert.deepEqual(opts.cacheMap.get(REQUEST_IPV6.ip), REQUEST_IPV6);
42
40
  });
43
41
  });
@@ -0,0 +1,56 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import {describe, it} from 'mocha';
4
+
5
+ import {ip2geo} from '../app/index.js';
6
+
7
+ import {getCurrentFilename, getTestFolder} from './helpers/path.js';
8
+ import {removeCacheFolder} from './shared/fs.js';
9
+
10
+ const testName = getCurrentFilename(import.meta.url);
11
+
12
+ describe(testName, () => {
13
+ const firstReqIps = [
14
+ '10.10.10.10',
15
+ '20.20.20.20',
16
+ '30.30.30.30',
17
+ '40.40.40.40',
18
+ '50.50.50.50',
19
+ ];
20
+
21
+ const secondReqIps = ['60.60.60.60'];
22
+
23
+ const opts = {
24
+ cacheDir: getTestFolder(testName),
25
+ cacheMap: new Map(),
26
+ cacheMapMaxEntries: 2,
27
+ };
28
+
29
+ it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
30
+
31
+ firstReqIps.forEach(ip => {
32
+ it(`should request geo for IP: "${ip}"`, async () => {
33
+ await ip2geo(ip, opts);
34
+ });
35
+ });
36
+
37
+ it(`should have ${opts.cacheMapMaxEntries} correct cache entries`, () => {
38
+ assert.equal(opts.cacheMap.size, opts.cacheMapMaxEntries);
39
+ assert.deepEqual([...opts.cacheMap.keys()], firstReqIps.slice(-opts.cacheMapMaxEntries));
40
+ });
41
+
42
+ secondReqIps.forEach(ip => {
43
+ it(`should request geo for IP: "${ip}"`, async () => {
44
+ await ip2geo(ip, opts);
45
+ });
46
+ });
47
+
48
+ it(`should have ${opts.cacheMapMaxEntries} correct cache entries`, () => {
49
+ assert.equal(opts.cacheMap.size, opts.cacheMapMaxEntries);
50
+
51
+ assert.deepEqual(
52
+ [...opts.cacheMap.keys()],
53
+ [firstReqIps, secondReqIps].flat().slice(-opts.cacheMapMaxEntries),
54
+ );
55
+ });
56
+ });
@@ -0,0 +1,33 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import {describe, it} from 'mocha';
4
+
5
+ import {cacheStorage, ip2geo} from '../app/index.js';
6
+
7
+ import {REQUEST_IPV4} from './helpers/consts.js';
8
+ import {getCurrentFilename, getTestFolder} from './helpers/path.js';
9
+ import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
10
+
11
+ const testName = getCurrentFilename(import.meta.url);
12
+
13
+ describe(testName, () => {
14
+ const opts = {
15
+ cacheDir: getTestFolder(testName),
16
+ cacheMapMaxEntries: 0,
17
+ };
18
+
19
+ it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
20
+
21
+ it(`should return correct response for IP: "${REQUEST_IPV4.ip}"`, async () => {
22
+ const data = await ip2geo(REQUEST_IPV4.ip, opts);
23
+ assert.deepEqual(data, REQUEST_IPV4);
24
+ });
25
+
26
+ it('should have cache file', () => checkCacheFile({
27
+ response: REQUEST_IPV4,
28
+ }));
29
+
30
+ it('should not have cache entries', () => {
31
+ assert.equal(cacheStorage.size, 0);
32
+ });
33
+ });
@@ -30,4 +30,9 @@ describe(testName, () => {
30
30
  ...opts,
31
31
  response: REQUEST_IPV4,
32
32
  }));
33
+
34
+ it('should have 1 correct cache entry', () => {
35
+ assert.equal(opts.cacheMap.size, 1);
36
+ assert.deepEqual(opts.cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
37
+ });
33
38
  });
@@ -11,13 +11,10 @@ import {removeCacheFolder} from './shared/fs.js';
11
11
  const testName = getCurrentFilename(import.meta.url);
12
12
 
13
13
  describe(testName, () => {
14
- const response = {ip: '10.10.10.10'};
15
-
16
- Object.keys(REQUEST_IPV4).forEach(elem => {
17
- if (elem !== 'ip') {
18
- response[elem] = undefined;
19
- }
20
- });
14
+ const responses = [
15
+ {ip: '10.10.10.10'},
16
+ {ip: 'test'},
17
+ ];
21
18
 
22
19
  const opts = {
23
20
  cacheDir: getTestFolder(testName),
@@ -26,8 +23,16 @@ describe(testName, () => {
26
23
 
27
24
  it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
28
25
 
29
- it(`should return empty response for IP: "${response.ip}"`, async () => {
30
- const data = await ip2geo(response.ip, opts);
31
- assert.deepEqual(data, response);
26
+ responses.forEach(response => {
27
+ Object.keys(REQUEST_IPV4).forEach(elem => {
28
+ if (elem !== 'ip') {
29
+ response[elem] = undefined;
30
+ }
31
+ });
32
+
33
+ it(`should return empty response for IP: "${response.ip}"`, async () => {
34
+ const data = await ip2geo(response.ip, opts);
35
+ assert.deepEqual(data, response);
36
+ });
32
37
  });
33
38
  });
@@ -20,7 +20,6 @@ describe(testName, () => {
20
20
 
21
21
  it(`should return correct response for IP: "${REQUEST_IPV6.ip}"`, async () => {
22
22
  const data = await ip2geo(REQUEST_IPV6.ip, opts);
23
-
24
23
  assert.deepEqual(data, REQUEST_IPV6);
25
24
  });
26
25
 
File without changes