Skip to content

Commit e9795fa

Browse files
authored
Merge pull request #14 from ConnerTechnology/update_cache
Make cache configurable and cache bulk lookup responses
2 parents f0c6610 + bbc0034 commit e9795fa

5 files changed

Lines changed: 69 additions & 45 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "ipdata",
33
"version": "2.0.0",
44
"description": "JavaScript library to gather information for an ip using https://ipdata.co.",
5-
"main": "./lib/index.js",
5+
"main": "./lib/ipdata.js",
66
"scripts": {
77
"prebuild": "del lib",
88
"build": "tsc",
@@ -24,7 +24,7 @@
2424
"is-ip": "^3.1.0",
2525
"lodash": "4.17.15",
2626
"lru-cache": "5.1.1",
27-
"tslib": "1.9.3",
27+
"tslib": "1.10.0",
2828
"url-join": "4.0.0"
2929
},
3030
"devDependencies": {

src/ipdata.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import LRU from 'lru-cache';
77

88
const CACHE_MAX = 4096; // max number of items
99
const CACHE_MAX_AGE = 1000 * 60 * 60 * 24; // 24 hours
10-
const cache = new LRU<string, LookupResponse>({ max: CACHE_MAX, maxAge: CACHE_MAX_AGE });
1110
const DEFAULT_IP = 'DEFAULT_IP';
1211
const VALID_FIELDS = [
1312
'ip',
@@ -67,12 +66,10 @@ function isValidFields(fields: string[]): boolean {
6766
return true;
6867
}
6968

70-
// export function clearCache(ip?: string): void {
71-
// if (isValidIP(ip)) {
72-
// return cache.del(ip);
73-
// }
74-
// return cache.reset();
75-
// }
69+
export interface CacheConfig {
70+
max?: number;
71+
maxAge?: number;
72+
}
7673

7774
export interface LookupResponse {
7875
ip: string;
@@ -139,15 +136,15 @@ interface IPDataParams {
139136

140137
export default class IPData {
141138
apiKey?: string;
142-
useCache?: boolean;
139+
cache?: LRU<string, LookupResponse>;
143140

144-
constructor(apiKey: string, useCache = true) {
141+
constructor(apiKey: string, cacheConfig?: CacheConfig) {
145142
if (!isString(apiKey)) {
146143
throw new Error('An API key is required.');
147144
}
148145

149146
this.apiKey = apiKey;
150-
this.useCache = useCache === true;
147+
this.cache = new LRU<string, LookupResponse>({ max: CACHE_MAX, maxAge: CACHE_MAX_AGE, ...cacheConfig });
151148
}
152149

153150
async lookup(ip?: string, selectField?: string, fields?: string[]): Promise<LookupResponse> {
@@ -158,8 +155,8 @@ export default class IPData {
158155
throw new Error(`${ip} is an invalid IP address.`);
159156
}
160157

161-
if (this.useCache && cache.has(ip || DEFAULT_IP)) {
162-
return cache.get(ip || DEFAULT_IP);
158+
if (this.cache.has(ip || DEFAULT_IP)) {
159+
return this.cache.get(ip || DEFAULT_IP);
163160
}
164161

165162
if (selectField && fields) {
@@ -182,11 +179,7 @@ export default class IPData {
182179
data = { [selectField]: response.data, status: response.status };
183180
}
184181

185-
if ((!selectField || !fields) && this.useCache) {
186-
cache.set(ip || DEFAULT_IP, data);
187-
} else {
188-
return data;
189-
}
182+
this.cache.set(ip || DEFAULT_IP, data);
190183
} catch (e) {
191184
const { response } = e as AxiosError;
192185
if (response) {
@@ -195,11 +188,13 @@ export default class IPData {
195188
throw e;
196189
}
197190

198-
return cache.get(ip || DEFAULT_IP);
191+
return this.cache.get(ip || DEFAULT_IP);
199192
}
200193

201194
async bulkLookup(ips: string[], fields?: string[]): Promise<BulkLookupResponse> {
202195
const params: IPDataParams = { 'api-key': this.apiKey };
196+
const responses: LookupResponse[] = [];
197+
const bulk = [];
203198

204199
if (ips.length < 2) {
205200
throw new Error('Bulk Lookup requires more than 1 IP Address in the payload.');
@@ -209,15 +204,30 @@ export default class IPData {
209204
if (!isValidIP(ip)) {
210205
throw new Error(`${ip} is an invalid IP address.`);
211206
}
207+
208+
if (this.cache.has(ip)) {
209+
responses.push(this.cache.get(ip));
210+
} else {
211+
bulk.push(ip);
212+
}
212213
});
213214

214215
if (fields && isValidFields(fields)) {
215216
params.fields = fields.join(',');
216217
}
217218

218219
try {
219-
const response = await axios.post(urljoin(BASE_URL, 'bulk'), ips, { params });
220-
return { responses: response.data, status: response.status };
220+
let result: BulkLookupResponse = { responses, status: 200 };
221+
222+
if (bulk.length > 0) {
223+
const response = await axios.post(urljoin(BASE_URL, 'bulk'), bulk, { params });
224+
response.data.forEach(info => {
225+
this.cache.set(info.ip, info);
226+
});
227+
result = { responses: [...responses, ...response.data], status: response.status };
228+
}
229+
230+
return result;
221231
} catch (e) {
222232
const { response } = e as AxiosError;
223233
if (response) {

test/ipdata.test.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const API_KEY = 'test';
44

55
describe('constructor()', () => {
66
it('should throw an error if an apiKey is not provided', () => {
7+
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
78
// @ts-ignore
89
expect(() => new IPData()).toThrowError('An API key is required.');
910
});
@@ -12,28 +13,22 @@ describe('constructor()', () => {
1213
const ipdata = new IPData(API_KEY);
1314
expect(ipdata.apiKey).toEqual(API_KEY);
1415
});
15-
16-
it('should turn on the cache by default', () => {
17-
const ipdata = new IPData(API_KEY);
18-
expect(ipdata.useCache).toEqual(true);
19-
});
20-
21-
it('should turn off the cache', () => {
22-
const ipdata = new IPData(API_KEY, false);
23-
expect(ipdata.useCache).toEqual(false);
24-
});
2516
})
2617

2718
describe('lookup()', () => {
28-
const ipdata = new IPData(API_KEY, false);
19+
const ipdata = new IPData(API_KEY);
2920
const IP = '1.1.1.1';
3021

31-
it('should throw an error if the ip is invalid', async () => {
22+
afterEach(() => {
23+
return ipdata.cache.reset();
24+
});
25+
26+
it('should throw an error if the ip address is invalid', async () => {
3227
const badIP = '1.1.11';
3328
await expect(ipdata.lookup(badIP)).rejects.toThrowError(`${badIP} is an invalid IP address.`);
3429
});
3530

36-
it('should return default information when no ip is provided', async () => {
31+
it('should return default information when no ip address is provided', async () => {
3732
const info = await ipdata.lookup();
3833
expect(info).toHaveProperty('ip');
3934
expect(info).toHaveProperty('status');
@@ -51,12 +46,26 @@ describe('lookup()', () => {
5146
expect(info).toHaveProperty('status');
5247
});
5348

54-
it('should set an ip in the uri', async () => {
49+
it('should cache the ip address info for the default ip address', async () => {
50+
await ipdata.lookup();
51+
const info = ipdata.cache.get('DEFAULT_IP');
52+
expect(info).toHaveProperty('ip');
53+
expect(info).toHaveProperty('status');
54+
});
55+
56+
it('should accept an ip address', async () => {
5557
const info = await ipdata.lookup(IP);
5658
expect(info).toHaveProperty('ip', IP);
5759
expect(info).toHaveProperty('status');
5860
});
5961

62+
it('should cache the ip address info', async () => {
63+
await ipdata.lookup(IP);
64+
const info = ipdata.cache.get(IP);
65+
expect(info).toHaveProperty('ip', IP);
66+
expect(info).toHaveProperty('status');
67+
});
68+
6069
it('should throw an error if a selectField and fields is provided', async () => {
6170
await expect(ipdata.lookup(null, 'field', ['field'])).rejects.toThrowError(
6271
'The selectField and fields parameters cannot be used at the same time.',
@@ -97,24 +106,28 @@ describe('lookup()', () => {
97106
});
98107

99108
describe('bulkLookup()', () => {
100-
const ipdata = new IPData(API_KEY, false);
109+
const ipdata = new IPData(API_KEY);
101110
const IP1 = '1.1.1.1';
102111
const IP2 = '8.8.8.8';
103112

104-
it('should throw an error if less than 2 ips are provided', async () => {
113+
afterEach(() => {
114+
return ipdata.cache.reset();
115+
});
116+
117+
it('should throw an error if less than 2 ip addresses are provided', async () => {
105118
const ips = [IP1];
106119
await expect(ipdata.bulkLookup(ips)).rejects.toThrowError(
107120
'Bulk Lookup requires more than 1 IP Address in the payload.',
108121
);
109122
});
110123

111-
it('should throw an error if an ip is invalid', async () => {
124+
it('should throw an error if an ip address is invalid', async () => {
112125
const badIP = '1.1.11';
113126
const ips = [badIP, IP2];
114127
await expect(ipdata.bulkLookup(ips)).rejects.toThrowError(`${badIP} is an invalid IP address.`);
115128
});
116129

117-
it('should return info for the ips', async () => {
130+
it('should return info for the ip addresses', async () => {
118131
const info = await ipdata.bulkLookup([IP1, IP2]);
119132
expect(info).toHaveProperty('responses');
120133
expect(info).toHaveProperty('status');

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"target": "es5"
1313
},
1414
"include": [
15-
"src/**/*"
15+
"src/**/*",
16+
"test/**/*"
1617
]
1718
}

0 commit comments

Comments
 (0)