Skip to content

Commit ba81605

Browse files
committedApr 25, 2019
Initial Commit
1 parent 400174b commit ba81605

10 files changed

+5485
-1
lines changed
 

‎.editorconfig

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# EditorConfig is awesome: http://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
# Unix-style newlines with a newline ending every file
7+
[*]
8+
end_of_line = lf
9+
insert_final_newline = true
10+
11+
# 2 space indentation
12+
[**.*]
13+
indent_style = space
14+
indent_size = 2

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ typings/
5959

6060
# next.js build output
6161
.next
62+
63+
# dist
64+
dist/

‎.prettierrc.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"printWidth": 120
4+
}

‎README.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,33 @@
1-
# pythagoreancache
1+
# Pythagorean Cache
2+
23
🍷A cache that dumps when full or at an interval
4+
5+
[![cup](https://upload.wikimedia.org/wikipedia/commons/a/a9/Physagorian_Pythagoras_Greedy_Tantalus_cup_05.svg)](https://en.wikipedia.org/wiki/Pythagorean_cup)
6+
7+
## Example
8+
9+
```typescript
10+
import { PythagoreanCache } from 'pythagorean-cache';
11+
12+
// dump when size reaches 10
13+
const cache = new PythagoreanCache<string>({ size: 10 });
14+
15+
cache.on('dump', () => {
16+
/* do something with 10 items */
17+
});
18+
19+
for (let x = 0; x < 10; x++) {
20+
cache.push(`Hello ${x}`);
21+
}
22+
```
23+
24+
## Options
25+
26+
- size: The size of the cache. When the size is reached, a `dump` event is fired with all the items in the cache and then emptied
27+
- interval: The number of microseconds to wait before firing a `dump` event. This can be used to dump the cache at regular intervals.
28+
29+
These options can be combined, allowing you to create a cache that dumps at a certain size _or_ at a certain interval.
30+
31+
## Inspired Use Case
32+
33+
I was receiving a bursting stream of events I needed to add to a database. Instead of inserting each one as they come in, it was more efficient to do bulk inserts at intervals/sizes. This way that database was protected from burst events and I could expect the events to be inserted within a certain amount of time regardless.

‎jest.config.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
collectCoverage: true
5+
};

‎package-lock.json

+5,203
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "pythagorean-cache",
3+
"version": "1.0.0",
4+
"description": "A cache that empties when full or at an interval",
5+
"main": "dist/index.js",
6+
"files": [
7+
"dist/"
8+
],
9+
"scripts": {
10+
"test": "npm-run-all test:*",
11+
"test:unit": "jest",
12+
"test:prettier": "prettier -c \"src/**/*.ts\"",
13+
"build": "npm-run-all build:*",
14+
"build:clean": "rimraf dist/*",
15+
"build:transpile": "tsc"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "git+https://github.com/shaunburdick/pythagorean-cache.git"
20+
},
21+
"keywords": [
22+
"cache",
23+
"interval",
24+
"pythagorean",
25+
"dump"
26+
],
27+
"author": "Shaun Burdick <npm@shaunburdick.com>",
28+
"license": "MIT",
29+
"bugs": {
30+
"url": "https://github.com/shaunburdick/pythagorean-cache/issues"
31+
},
32+
"homepage": "https://github.com/shaunburdick/pythagorean-cache#readme",
33+
"devDependencies": {
34+
"@types/jest": "^24.0.11",
35+
"@types/node": "^11.13.7",
36+
"jest": "^24.7.1",
37+
"npm-run-all": "^4.1.5",
38+
"prettier": "^1.17.0",
39+
"rimraf": "^2.6.3",
40+
"ts-jest": "^24.0.2",
41+
"typescript": "^3.4.5"
42+
}
43+
}

‎src/__tests__/index.test.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { PythagoreanCache } from '..';
2+
3+
describe('PythagoreanCache', () => {
4+
it('should throw an error if you do not specify a size or interval', done => {
5+
expect(() => new PythagoreanCache({})).toThrow(/You must specify either a size or interval \(or both\)/);
6+
done();
7+
});
8+
9+
it('dump(): should emit items', done => {
10+
const cq = new PythagoreanCache<string>({ size: 10 });
11+
cq.push('foo');
12+
13+
cq.once('dump', items => {
14+
expect(items).toEqual(['foo']);
15+
done();
16+
});
17+
18+
cq.dump();
19+
});
20+
21+
it('should emit items when size is reached', done => {
22+
const pushItems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
23+
const cq = new PythagoreanCache<number>({ size: pushItems.length });
24+
25+
cq.once('dump', items => {
26+
expect(items).toEqual(pushItems);
27+
done();
28+
});
29+
30+
pushItems.forEach(item => cq.push(item));
31+
});
32+
33+
it('should emit items when interval is reached', done => {
34+
const pushItems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
35+
const cq = new PythagoreanCache<number>({ interval: 10 });
36+
37+
cq.once('dump', items => {
38+
cq.stopInterval();
39+
expect(items).toEqual(pushItems);
40+
done();
41+
});
42+
43+
pushItems.forEach(item => cq.push(item));
44+
});
45+
});

‎src/index.ts

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { EventEmitter } from 'events';
2+
3+
export interface PythagoreanCacheOpts {
4+
/**
5+
* Dump cache when queue reaches this size
6+
*/
7+
size?: number;
8+
9+
/**
10+
* Dump cache every X milliseconds
11+
*/
12+
interval?: number;
13+
}
14+
15+
/**
16+
* @link https://en.wikipedia.org/wiki/Pythagorean_cup
17+
*
18+
* Creates a cache that will emit its items on:
19+
* - size: When the cache grows to this size, it will emit the items and reset
20+
* - interval: Every X milliseconds it will emit the items (if any) and reset
21+
*
22+
* @emits dump When the size/interval is met, it will emit the items in the cache and reset
23+
*
24+
* @class PythagoreanCache
25+
* @template T
26+
*/
27+
export class PythagoreanCache<T = any> extends EventEmitter {
28+
private queue: T[] = [];
29+
private interval: NodeJS.Timer;
30+
31+
get length() {
32+
return this.queue.length;
33+
}
34+
35+
constructor(private opts: PythagoreanCacheOpts) {
36+
super();
37+
38+
if (!opts.size && !opts.interval) {
39+
throw new Error('You must specify either a size or interval (or both)');
40+
}
41+
42+
this.startInterval();
43+
}
44+
45+
/**
46+
* Push an item onto the cache
47+
*
48+
* @param items The items to push onto the cache
49+
* @returns the new size of the cache
50+
*/
51+
push(...items: T[]): number {
52+
this.queue.push(...items);
53+
if (this.checkLimit()) this.dump();
54+
return this.length;
55+
}
56+
57+
/**
58+
* Check of the cache has reached its size limit
59+
*
60+
* @returns true if limit is reached
61+
*/
62+
checkLimit(): boolean {
63+
return this.opts.size ? this.length >= this.opts.size : false;
64+
}
65+
66+
/**
67+
* Emit the items in the cache, it will do nothing if there are no items in cache
68+
*/
69+
dump(): void {
70+
if (this.length > 0) {
71+
const items = this.queue.splice(0, this.opts.size || this.length);
72+
this.emit('dump', items);
73+
}
74+
}
75+
76+
/**
77+
* Change the size. This will trigger a dump event if newSize is smaller than the current length
78+
*
79+
* @param newSize The new size
80+
*/
81+
setSize(newSize: number): void {
82+
this.opts.size = newSize;
83+
if (this.checkLimit()) this.dump();
84+
}
85+
86+
/**
87+
* Change the interval. This will cause the interval job to restart at zero.
88+
*
89+
* @param newInterval The new interval
90+
*/
91+
setInterval(newInterval: number): void {
92+
this.opts.interval = newInterval;
93+
this.startInterval();
94+
}
95+
96+
/**
97+
* Start the interval dump (if set)
98+
*/
99+
startInterval() {
100+
this.stopInterval();
101+
102+
if (this.opts.interval) {
103+
this.interval = setInterval(() => {
104+
this.dump();
105+
}, this.opts.interval);
106+
}
107+
}
108+
109+
/**
110+
* Stop the interval check
111+
*/
112+
stopInterval(): void {
113+
if (this.interval) {
114+
clearInterval(this.interval);
115+
}
116+
}
117+
}

‎tsconfig.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2015",
4+
"module": "commonjs",
5+
"moduleResolution": "node",
6+
"declaration": true,
7+
"inlineSourceMap": true,
8+
"inlineSources": true,
9+
"removeComments": false,
10+
"pretty": true,
11+
"strictNullChecks": true,
12+
"lib": ["es2015", "es2016"],
13+
"outDir": "dist",
14+
"sourceRoot": "src/"
15+
},
16+
"exclude": ["**/__tests__/*"],
17+
"compileOnSave": false,
18+
"buildOnSave": false
19+
}

0 commit comments

Comments
 (0)
Please sign in to comment.