cacheable 0.2.7 → 0.3.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/LICENSE +19 -0
- package/README.md +166 -137
- package/package.json +27 -27
- package/src/index.ts +122 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +109 -0
- package/.npmignore +0 -3
- package/.travis.yml +0 -4
- package/Makefile +0 -7
- package/index.js +0 -1
- package/lib/cacheable.js +0 -162
- package/lib/registry.js +0 -164
- package/test/cacheable.test.js +0 -129
- package/test/mocha.opts +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
|
5
|
+
deal in the Software without restriction, including without limitation the
|
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,189 +1,218 @@
|
|
|
1
|
-
|
|
1
|
+
[<img align="center" src="https://jaredwray.com/images/cacheable_white.svg" alt="keyv">](https://github.com/jaredwray/cacheable)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# cacheable
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Manage all cache keys in one place, use a simple `._clearCache()` to purge cache.
|
|
5
|
+
> Simple Caching Engine using Keyv
|
|
7
6
|
|
|
7
|
+
[](https://codecov.io/gh/jaredwray/cacheable)
|
|
8
|
+
[](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
|
|
9
|
+
[](https://www.npmjs.com/package/cacheable)
|
|
10
|
+
[](https://www.npmjs.com/package/cacheable)
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
`cacheable` is a simple caching engine that uses [Keyv](https://keyv.org) as the storage engine. It is designed to be simple to use and extend. Here are some of the features:
|
|
13
|
+
* Simple to use with robust API
|
|
14
|
+
* Not bloated with additional modules
|
|
15
|
+
* Extendable to your own caching engine
|
|
16
|
+
* Scalable and trusted storage engine by Keyv
|
|
17
|
+
* Statistics built in by default
|
|
18
|
+
* Hooks and Events to extend functionality
|
|
19
|
+
* Comprehensive testing and code coverage
|
|
20
|
+
* Maintained and supported
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
var Redis = require('redis');
|
|
13
|
-
var Cacheable = require('cacheable');
|
|
14
|
-
|
|
15
|
-
var client = Redis.createClient();
|
|
16
|
-
var cached = Cacheable({
|
|
17
|
-
client: client,
|
|
18
|
-
ttl: 60, // set a default max age of one minute for `cached.set`
|
|
19
|
-
prefix: 'myapp:' // the prefix for every cache key
|
|
20
|
-
});
|
|
22
|
+
## Getting Started
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
cached.set(key, value, 300, callback)
|
|
24
|
-
cached.get(key, callback)
|
|
25
|
-
cached.del(['abc', 'aba'], callback)
|
|
26
|
-
```
|
|
24
|
+
`cacheable` is primarily used as an extension to you caching engine with a robust storage backend [Keyv](https://keyv.org), Memonization, Hooks, Events, and Statistics.
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```javascript
|
|
31
|
-
// Get remote content that expires in 3600 seconds
|
|
32
|
-
var getUrlContent = cached.wrap(function(url, callback) {
|
|
33
|
-
request(url, function() {
|
|
34
|
-
// ...
|
|
35
|
-
})
|
|
36
|
-
}, 'url-{0}', 3600)
|
|
26
|
+
```bash
|
|
27
|
+
npm install cacheable
|
|
37
28
|
```
|
|
38
29
|
|
|
39
|
-
|
|
30
|
+
## Basic Usage
|
|
40
31
|
|
|
41
32
|
```javascript
|
|
42
|
-
|
|
43
|
-
this.attributes = data
|
|
44
|
-
}
|
|
33
|
+
import { Cacheable } from 'cacheable';
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
const cacheable = new Cacheable();
|
|
36
|
+
cacheable.set('key', 'value', 1000);
|
|
37
|
+
const value = cacheable.get('key');
|
|
38
|
+
```
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
User.get = function(user_id, callback) {
|
|
52
|
-
// get the user from data base
|
|
53
|
-
// ...
|
|
54
|
-
var user = new User(data)
|
|
40
|
+
## Extending Your own Caching Engine
|
|
55
41
|
|
|
56
|
-
|
|
57
|
-
}
|
|
42
|
+
```javascript
|
|
43
|
+
import { Cacheable } from 'cacheable';
|
|
58
44
|
|
|
59
|
-
|
|
60
|
-
|
|
45
|
+
export class MyCache extends Cacheable {
|
|
46
|
+
constructor() {
|
|
47
|
+
super();
|
|
48
|
+
}
|
|
61
49
|
}
|
|
62
|
-
|
|
63
|
-
// register the constructor first
|
|
64
|
-
cached.register(User)
|
|
65
|
-
|
|
66
|
-
// enable cache for `User.get` method
|
|
67
|
-
// So when you call `User.get(some_id)`, it will fetch data
|
|
68
|
-
// from cache first, when cache not found, then the original function will be called.
|
|
69
|
-
User.enableCache('get', '{_model_}:{0}') // '{0}' means the `arguments[0]`
|
|
70
|
-
|
|
71
|
-
// You can also enable cache for an instance method
|
|
72
|
-
User.enableCache('.getPostIds', '{_model_}:posts-{0}-{1}')
|
|
73
50
|
```
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### cached.register(cls, name)
|
|
52
|
+
From here you now how the ability to use the `cacheable` API. You can also extend the API to add your own functionality.
|
|
78
53
|
|
|
79
|
-
|
|
80
|
-
how to restore the data as a proper JavaScript Object.
|
|
54
|
+
## Storage Adapters and Keyv
|
|
81
55
|
|
|
82
|
-
|
|
83
|
-
then cached will use this name.
|
|
56
|
+
To set Keyv as the storage engine, you can do the following:
|
|
84
57
|
|
|
85
58
|
```javascript
|
|
86
|
-
|
|
59
|
+
import { Cacheable } from 'cacheable';
|
|
60
|
+
import Keyv from 'keyv';
|
|
61
|
+
|
|
62
|
+
export class MyCache extends Cacheable {
|
|
63
|
+
constructor() {
|
|
64
|
+
super(new Keyv('redis://user:pass@localhost:6379'));
|
|
65
|
+
}
|
|
87
66
|
}
|
|
88
|
-
cached.register(Book, 'Book')
|
|
89
67
|
```
|
|
90
68
|
|
|
91
|
-
|
|
92
|
-
The `.toJSON` will be extended by `cache.register`, the output object will always have a property `__cachedname`,
|
|
93
|
-
as is the constructor's modelName. You can add a `.toObject = .toJSON`, and use `.toObject` whenever you need a clean object.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
If an `._unpickle` method is also defined, it will be called each time the object is restored from cache.
|
|
97
|
-
|
|
98
|
-
That is:
|
|
69
|
+
or you can do it at the property level:
|
|
99
70
|
|
|
100
71
|
```javascript
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return item
|
|
104
|
-
```
|
|
105
|
-
Note that it would be impossible to unpickle a cache if the constructor's name was changed.
|
|
72
|
+
import { Cacheable } from 'cacheable';
|
|
73
|
+
import Keyv from 'keyv';
|
|
106
74
|
|
|
107
|
-
|
|
108
|
-
|
|
75
|
+
export class MyCache extends Cacheable {
|
|
76
|
+
constructor() {
|
|
77
|
+
super();
|
|
109
78
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
var self = this
|
|
113
|
-
// destroy the item from database
|
|
114
|
-
db.destroy(..., function() {
|
|
115
|
-
// then clear the cache
|
|
116
|
-
self._clearCache(callback)
|
|
117
|
-
})
|
|
79
|
+
this.store = new Keyv('redis://user:pass@localhost:6379');
|
|
80
|
+
}
|
|
118
81
|
}
|
|
119
82
|
```
|
|
120
83
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Wrap an standard nodejs async function(which should have a `callback(err, result)` as the last parameter).
|
|
124
|
-
The `ttl` is in seconds. If no `ttl` set, the cache will never automatically expire, even it an `options.ttl`
|
|
125
|
-
is passed when you do `new Cached()`.
|
|
84
|
+
## Statistics
|
|
126
85
|
|
|
127
|
-
|
|
86
|
+
To get statistics on your cache, you can do the following:
|
|
128
87
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{_model_}:{_fn_}
|
|
88
|
+
```javascript
|
|
89
|
+
import { Cacheable } from 'cacheable';
|
|
132
90
|
|
|
133
|
-
|
|
134
|
-
|
|
91
|
+
export class MyCache extends Cacheable {
|
|
92
|
+
constructor() {
|
|
93
|
+
super();
|
|
94
|
+
}
|
|
135
95
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
96
|
+
async getStats() {
|
|
97
|
+
return this.stats.getReport();
|
|
98
|
+
}
|
|
139
99
|
}
|
|
140
100
|
```
|
|
141
101
|
|
|
142
|
-
|
|
143
|
-
|
|
102
|
+
This will generate the following json object:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"cacheSize": 100,
|
|
107
|
+
"currentSize": 80,
|
|
108
|
+
"hits": 500,
|
|
109
|
+
"misses": 200,
|
|
110
|
+
"hitRate": 0.71,
|
|
111
|
+
"evictions": 50,
|
|
112
|
+
"averageLoadPenalty": 0.05,
|
|
113
|
+
"loadSuccessCount": 700,
|
|
114
|
+
"loadExceptionCount": 10,
|
|
115
|
+
"totalLoadTime": 3500,
|
|
116
|
+
"topHits": [
|
|
117
|
+
{
|
|
118
|
+
"key": "key1",
|
|
119
|
+
"value": "value1",
|
|
120
|
+
"lastAccessed": 1627593600000,
|
|
121
|
+
"accessCount": 50
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"key": "key2",
|
|
125
|
+
"value": "value2",
|
|
126
|
+
"lastAccessed": 1627593600000,
|
|
127
|
+
"accessCount": 45
|
|
128
|
+
}
|
|
129
|
+
// More items...
|
|
130
|
+
],
|
|
131
|
+
"leastUsed": [
|
|
132
|
+
{
|
|
133
|
+
"key": "key3",
|
|
134
|
+
"value": "value3",
|
|
135
|
+
"lastAccessed": 1627593600000,
|
|
136
|
+
"accessCount": 5
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"key": "key4",
|
|
140
|
+
"value": "value4",
|
|
141
|
+
"lastAccessed": 1627593600000,
|
|
142
|
+
"accessCount": 4
|
|
143
|
+
}
|
|
144
|
+
// More items...
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
* `cacheSize`: The maximum number of items that can be stored in the cache.
|
|
150
|
+
* `currentSize`: The current number of items in the cache.
|
|
151
|
+
hits: The number of cache hits. A cache hit occurs when the requested data is found in the cache.
|
|
152
|
+
* `misses`: The number of cache misses. A cache miss occurs when the requested data is not found in the cache and needs to be loaded.
|
|
153
|
+
* `hitRate`: The ratio of cache hits to the total number of cache lookups. This is a measure of the cache's effectiveness.
|
|
154
|
+
* `evictions`: The number of items that have been evicted from the cache, typically because the cache is full.
|
|
155
|
+
* `averageLoadPenalty`: The average time spent loading new values into the cache, typically measured in milliseconds. This could be calculated as totalLoadTime / (hits + misses).
|
|
156
|
+
* `loadSuccessCount`: The number of times cache loading has succeeded.
|
|
157
|
+
* `loadExceptionCount`: The number of times cache loading has failed due to exceptions.
|
|
158
|
+
* `totalLoadTime`: The total time spent loading new values into the cache, typically measured in milliseconds.
|
|
147
159
|
|
|
160
|
+
## Hooks and Events
|
|
148
161
|
|
|
149
|
-
|
|
162
|
+
The following hooks are available for you to extend the functionality of `cacheable`:
|
|
150
163
|
|
|
151
|
-
|
|
164
|
+
* `preSet`: This is called before the `set` method is called.
|
|
165
|
+
* `postSet`: This is called after the `set` method is called.
|
|
166
|
+
* `preSetMany`: This is called before the `setMany` method is called.
|
|
167
|
+
* `postSetMany`: This is called after the `setMany` method is called.
|
|
168
|
+
* `preGet`: This is called before the `get` method is called.
|
|
169
|
+
* `postGet`: This is called after the `get` method is called.
|
|
170
|
+
* `preGetMany`: This is called before the `getMany` method is called.
|
|
171
|
+
* `postGetMany`: This is called after the `getMany` method is called.
|
|
152
172
|
|
|
153
|
-
|
|
154
|
-
it's a class method.
|
|
173
|
+
An example of how to use these hooks:
|
|
155
174
|
|
|
156
175
|
```javascript
|
|
157
|
-
|
|
158
|
-
*
|
|
159
|
-
* List all ids
|
|
160
|
-
*
|
|
161
|
-
* Options:
|
|
162
|
-
*
|
|
163
|
-
* `limit`: limit per page
|
|
164
|
-
* `offset`: offset
|
|
165
|
-
*
|
|
166
|
-
*/
|
|
167
|
-
User.getAllIds = function(options, callback) {
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
User.prototype.getPostIds = function(start, limit, callback) {
|
|
171
|
-
// get user's posts
|
|
172
|
-
callback(null, [1,2,3...])
|
|
173
|
-
}
|
|
176
|
+
import { Cacheable } from 'cacheable';
|
|
174
177
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// You can omit the key, cacheable will automatically use the method name
|
|
180
|
-
User.enableCache('.getPostIds', 3600)
|
|
181
|
-
// KEY: '{_model_}:{id}:getTagsIds', expires in: 3600 seconds
|
|
178
|
+
const cacheable = new Cacheable();
|
|
179
|
+
cacheable.hooks.setHook('preSet', (key, value) => {
|
|
180
|
+
console.log(`preSet: ${key} ${value}`);
|
|
181
|
+
});
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
The following events are available for you to extend the functionality of `cacheable`:
|
|
185
185
|
|
|
186
|
+
* `set`: This is called when the `set` method is called.
|
|
187
|
+
* `setMany`: This is called when the `setMany` method is called.
|
|
188
|
+
* `get`: This is called when the `get` method is called.
|
|
189
|
+
* `getMany`: This is called when the `getMany` method is called.
|
|
190
|
+
* `clear`: This is called when the `clear` method is called.
|
|
191
|
+
* `has`: This is called when the `has` method is called.
|
|
192
|
+
* `disconnect`: This is called when the `disconnect` method is called.
|
|
193
|
+
* `error`: This is called when an error occurs.
|
|
186
194
|
|
|
187
|
-
##
|
|
195
|
+
## API
|
|
188
196
|
|
|
189
|
-
the
|
|
197
|
+
* `set(key, value, ttl?)`: Sets a value in the cache.
|
|
198
|
+
* `setMany([{key, value, ttl?}])`: Sets multiple values in the cache.
|
|
199
|
+
* `get(key)`: Gets a value from the cache.
|
|
200
|
+
* `has(key)`: Checks if a value exists in the cache.
|
|
201
|
+
* `getMany([keys])`: Gets multiple values from the cache.
|
|
202
|
+
* `delete(key)`: Deletes a value from the cache.
|
|
203
|
+
* `clear()`: Clears the cache.
|
|
204
|
+
* `disconnect()`: Disconnects from the cache.
|
|
205
|
+
* `getStats()`: Gets statistics from the cache.
|
|
206
|
+
* `setHook(hook, callback)`: Sets a hook.
|
|
207
|
+
* `deleteHook(hook)`: Removes a hook.
|
|
208
|
+
* `emitEvent(event, data)`: Emits an event.
|
|
209
|
+
* `on(event, callback)`: Listens for an event.
|
|
210
|
+
* `removeListener(event, callback)`: Removes a listener.
|
|
211
|
+
* `store`: The [Keyv](https://keyv.org) storage engine.
|
|
212
|
+
|
|
213
|
+
## How to Contribute
|
|
214
|
+
|
|
215
|
+
You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
|
|
216
|
+
|
|
217
|
+
## License and Copyright
|
|
218
|
+
MIT © Jared Wray - [https://github.com/jaredwray/cacheable/blob/main/LICENSE](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cacheable",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Simple Caching Engine using Keyv",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"repository": "https://github.com/jaredwray/cacheable.git",
|
|
8
|
+
"author": "Jared Wray <me@jaredwray.com>",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"private": false,
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"eslint": "^9.9.1",
|
|
13
|
+
"ts-node": "^10.9.2",
|
|
14
|
+
"typescript": "^5.5.4",
|
|
15
|
+
"xo": "^0.59.3"
|
|
8
16
|
},
|
|
9
17
|
"dependencies": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
18
|
+
"eventemitter3": "^5.0.1",
|
|
19
|
+
"hookified": "^0.7.0",
|
|
20
|
+
"keyv": "^5.0.1"
|
|
13
21
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"istanbul": "*"
|
|
22
|
+
"xo": {
|
|
23
|
+
"rules": {
|
|
24
|
+
"unicorn/prefer-event-target": "off"
|
|
25
|
+
}
|
|
19
26
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"cacheable",
|
|
28
|
-
"cache",
|
|
29
|
-
"cached"
|
|
30
|
-
],
|
|
31
|
-
"author": "Jesse Yang <kissmud@gmail.com>",
|
|
32
|
-
"license": "MIT"
|
|
33
|
-
}
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.build.json",
|
|
29
|
+
"test": "xo --fix",
|
|
30
|
+
"test:ci": "xo",
|
|
31
|
+
"clean": "rm -rf dist && rm -rf node_modules"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {Keyv} from 'keyv';
|
|
2
|
+
import {Hookified} from 'hookified';
|
|
3
|
+
|
|
4
|
+
type CacheableStatsItem = {
|
|
5
|
+
key: string;
|
|
6
|
+
lastAccessed: number;
|
|
7
|
+
accessCount: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type CacheableStats = {
|
|
11
|
+
cacheSize: number;
|
|
12
|
+
currentSize: number;
|
|
13
|
+
hits: number;
|
|
14
|
+
misses: number;
|
|
15
|
+
hitRate: number;
|
|
16
|
+
averageLoadPenalty: number;
|
|
17
|
+
loadSuccessCount: number;
|
|
18
|
+
loadExceptionCount: number;
|
|
19
|
+
totalLoadTime: number;
|
|
20
|
+
topHits: CacheableStatsItem[];
|
|
21
|
+
leastUsed: CacheableStatsItem[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export enum CacheableHooks {
|
|
25
|
+
BEFORE_SET = 'beforeSet',
|
|
26
|
+
AFTER_SET = 'afterSet',
|
|
27
|
+
BEFORE_SET_MANY = 'beforeSetMany',
|
|
28
|
+
AFTER_SET_MANY = 'afterSetMany',
|
|
29
|
+
BEFORE_GET = 'beforeGet',
|
|
30
|
+
AFTER_GET = 'afterGet',
|
|
31
|
+
BEFORE_GET_MANY = 'beforeGetMany',
|
|
32
|
+
AFTER_GET_MANY = 'afterGetMany',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export enum CacheableEvents {
|
|
36
|
+
ERROR = 'error',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export enum CacheableTieringModes {
|
|
40
|
+
PRIMARY_WITH_FAILOVER = 'primarySecondary',
|
|
41
|
+
ACID = 'allPrimary',
|
|
42
|
+
PRIMARY_ALL_FAILOVER = 'primaryAllFailover',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type CacheableOptions = {
|
|
46
|
+
store?: Keyv;
|
|
47
|
+
enableStats?: boolean;
|
|
48
|
+
enableOffline?: boolean;
|
|
49
|
+
nonBlocking?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export class Cacheable extends Hookified {
|
|
53
|
+
private _store: Keyv = new Keyv();
|
|
54
|
+
private readonly _stats: CacheableStats = {
|
|
55
|
+
currentSize: 0, cacheSize: 0, hits: 0, misses: 0, hitRate: 0, averageLoadPenalty: 0, loadSuccessCount: 0, loadExceptionCount: 0, totalLoadTime: 0, topHits: [], leastUsed: [],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
private _enableStats = false;
|
|
59
|
+
private _enableOffline = false;
|
|
60
|
+
|
|
61
|
+
constructor(keyv?: Keyv) {
|
|
62
|
+
super();
|
|
63
|
+
|
|
64
|
+
if (keyv) {
|
|
65
|
+
this._store = keyv;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public get enableStats(): boolean {
|
|
70
|
+
return this._enableStats;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public set enableStats(enabled: boolean) {
|
|
74
|
+
this._enableStats = enabled;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public get enableOffline(): boolean {
|
|
78
|
+
return this._enableOffline;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public set enableOffline(enabled: boolean) {
|
|
82
|
+
this._enableOffline = enabled;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public get store(): Keyv {
|
|
86
|
+
return this._store;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public set store(keyv: Keyv) {
|
|
90
|
+
this._store = keyv;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public get stats(): CacheableStats {
|
|
94
|
+
return this._stats;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public async get<T>(key: string): Promise<T | undefined> {
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
await this.hook(CacheableHooks.BEFORE_GET, key);
|
|
101
|
+
result = await this._store.get(key) as T;
|
|
102
|
+
await this.hook(CacheableHooks.AFTER_GET, {key, result});
|
|
103
|
+
} catch (error: unknown) {
|
|
104
|
+
await this.emit(CacheableEvents.ERROR, error);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public async set<T>(key: string, value: T, ttl?: number): Promise<boolean> {
|
|
111
|
+
let result = false;
|
|
112
|
+
try {
|
|
113
|
+
await this.hook(CacheableHooks.BEFORE_SET, {key, value, ttl});
|
|
114
|
+
result = await this._store.set(key, value, ttl);
|
|
115
|
+
await this.hook(CacheableHooks.AFTER_SET, {key, value, ttl});
|
|
116
|
+
} catch (error: unknown) {
|
|
117
|
+
await this.emit(CacheableEvents.ERROR, error);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
/* Visit https://aka.ms/tsconfig to read more about this file */
|
|
4
|
+
|
|
5
|
+
/* Projects */
|
|
6
|
+
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
|
7
|
+
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
|
8
|
+
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
|
9
|
+
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
|
10
|
+
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
|
11
|
+
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
|
12
|
+
|
|
13
|
+
/* Language and Environment */
|
|
14
|
+
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
|
15
|
+
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
|
16
|
+
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
|
17
|
+
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
|
18
|
+
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
|
19
|
+
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
|
20
|
+
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
|
21
|
+
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
|
22
|
+
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
|
23
|
+
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
|
24
|
+
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
|
25
|
+
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
|
26
|
+
|
|
27
|
+
/* Modules */
|
|
28
|
+
"module": "commonjs", /* Specify what module code is generated. */
|
|
29
|
+
// "rootDir": "./", /* Specify the root folder within your source files. */
|
|
30
|
+
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
|
31
|
+
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
|
32
|
+
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
|
33
|
+
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
|
34
|
+
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
|
35
|
+
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
|
36
|
+
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
|
37
|
+
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
|
38
|
+
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
|
39
|
+
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
|
40
|
+
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
|
41
|
+
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
|
42
|
+
// "resolveJsonModule": true, /* Enable importing .json files. */
|
|
43
|
+
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
|
44
|
+
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
|
45
|
+
|
|
46
|
+
/* JavaScript Support */
|
|
47
|
+
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
|
48
|
+
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
|
49
|
+
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
|
50
|
+
|
|
51
|
+
/* Emit */
|
|
52
|
+
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
|
53
|
+
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
|
54
|
+
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
|
55
|
+
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
|
56
|
+
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
|
57
|
+
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
|
58
|
+
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
|
59
|
+
// "removeComments": true, /* Disable emitting comments. */
|
|
60
|
+
// "noEmit": true, /* Disable emitting files from a compilation. */
|
|
61
|
+
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
|
62
|
+
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
|
63
|
+
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
|
64
|
+
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
|
65
|
+
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
|
66
|
+
"inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
|
67
|
+
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
|
68
|
+
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
|
69
|
+
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
|
70
|
+
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
|
71
|
+
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
|
72
|
+
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
|
73
|
+
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
|
74
|
+
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
|
75
|
+
|
|
76
|
+
/* Interop Constraints */
|
|
77
|
+
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
|
78
|
+
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
|
79
|
+
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
|
80
|
+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
|
81
|
+
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
|
82
|
+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
83
|
+
|
|
84
|
+
/* Type Checking */
|
|
85
|
+
"strict": true, /* Enable all strict type-checking options. */
|
|
86
|
+
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
|
87
|
+
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
|
88
|
+
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
|
89
|
+
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
|
90
|
+
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
|
91
|
+
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
|
92
|
+
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
|
93
|
+
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
|
94
|
+
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
|
95
|
+
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
|
96
|
+
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
|
97
|
+
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
|
98
|
+
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
|
99
|
+
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
|
100
|
+
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
|
101
|
+
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
|
102
|
+
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
|
103
|
+
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
|
104
|
+
|
|
105
|
+
/* Completeness */
|
|
106
|
+
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
|
107
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
108
|
+
}
|
|
109
|
+
}
|
package/.npmignore
DELETED
package/.travis.yml
DELETED
package/Makefile
DELETED
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./lib/cacheable.js');
|
package/lib/cacheable.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
var Storeman = require('storeman');
|
|
2
|
-
var keyf = require('keyf')
|
|
3
|
-
var debug = require('debug')('cacheable:debug')
|
|
4
|
-
|
|
5
|
-
var log = require('debug')('cacheable:log')
|
|
6
|
-
var __slice = Array.prototype.slice
|
|
7
|
-
|
|
8
|
-
// all key pattern definations
|
|
9
|
-
var allkeys = {}
|
|
10
|
-
var RE_KEY_PATTERN = /(%j)?{([\w\.]+)}/g
|
|
11
|
-
var _REALNAME = '__cachedname'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* A cached wrapper for async funtions
|
|
16
|
-
*
|
|
17
|
-
* Options:
|
|
18
|
-
*
|
|
19
|
-
* - `prefix` prefix for all keys under this cached manager
|
|
20
|
-
* - `silent` to ignore `storage.get` error
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
function Cacheable(options) {
|
|
24
|
-
if (!(this instanceof Cacheable)) return new Cacheable(options)
|
|
25
|
-
if (!options.prefix && options.prefix !== '') {
|
|
26
|
-
options.prefix = 'cached:'
|
|
27
|
-
}
|
|
28
|
-
this.silent = options.silent === false ? false : true
|
|
29
|
-
this.helpers = {}
|
|
30
|
-
Storeman.call(this, options);
|
|
31
|
-
}
|
|
32
|
-
// Inherit Storeman to get a bunch of `get`, `set` methods
|
|
33
|
-
require('util').inherits(Cacheable, Storeman)
|
|
34
|
-
|
|
35
|
-
Cacheable.prototype.debug = debug
|
|
36
|
-
// default key for wrapping standalone functions
|
|
37
|
-
Cacheable.prototype.DEFAULT_KEY = '{_fn_}:%j{0}'
|
|
38
|
-
|
|
39
|
-
Cacheable.prototype._applykey = function applykey(ctx, key, fn, args) {
|
|
40
|
-
return Cacheable.keyReplacer.call(ctx, key, fn, args)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Wraps an async funtion, automaticly cache the result
|
|
45
|
-
*
|
|
46
|
-
* The async function must have a signature of `fn(arg1, [arg2...,] callback)`.
|
|
47
|
-
* The `arg1` can be an options object, if `options.fresh` is passed as true,
|
|
48
|
-
* cache will not be used.
|
|
49
|
-
*/
|
|
50
|
-
Cacheable.prototype.wrap = function(fn, key, ttl, ctx) {
|
|
51
|
-
var cached = this
|
|
52
|
-
|
|
53
|
-
if ('number' == typeof key) {
|
|
54
|
-
// second parameter as ttl
|
|
55
|
-
ctx = ttl
|
|
56
|
-
ttl = key
|
|
57
|
-
key = null
|
|
58
|
-
}
|
|
59
|
-
if ('function' == typeof key || 'object' == typeof key) {
|
|
60
|
-
// second parameter as context
|
|
61
|
-
ctx = key
|
|
62
|
-
key = null
|
|
63
|
-
}
|
|
64
|
-
key = key || cached.DEFAULT_KEY
|
|
65
|
-
|
|
66
|
-
if (!getName(fn) && ~key.indexOf('{_fn_}')) {
|
|
67
|
-
throw new Error('Cache key referred to "{_fn_}", but the function doesn\'t have a name')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// try formating the key in advance
|
|
71
|
-
var uniqkey = cached._applykey(ctx, key, fn, [])
|
|
72
|
-
if (uniqkey in allkeys) {
|
|
73
|
-
log('Possible key conflict -> fn: [%s], key: %s', getName(fn), uniqkey)
|
|
74
|
-
} else {
|
|
75
|
-
allkeys[uniqkey] = null
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
debug('wrapping `%s`, key: "%s", ttl: %s', getName(fn) || '[anonymous]', uniqkey, ttl)
|
|
79
|
-
|
|
80
|
-
return function cacheWrapped() {
|
|
81
|
-
var self = ctx || this
|
|
82
|
-
var args = __slice.apply(arguments)
|
|
83
|
-
var callback = args[args.length - 1]
|
|
84
|
-
// the real key
|
|
85
|
-
var _key = cached._applykey(self, key, fn, args)
|
|
86
|
-
|
|
87
|
-
// doesn't need a callback
|
|
88
|
-
// just do the job as there is no cache
|
|
89
|
-
if (typeof callback !== 'function') {
|
|
90
|
-
debug('called with no callback, skip "%s"', _key)
|
|
91
|
-
return run()
|
|
92
|
-
}
|
|
93
|
-
// when `options.fresh` is passed as true,
|
|
94
|
-
// don't use cache
|
|
95
|
-
if ('object' == typeof args[0] && args[0].fresh) {
|
|
96
|
-
return run()
|
|
97
|
-
}
|
|
98
|
-
// cache key is not fully formatted means some args are not passed in
|
|
99
|
-
if (_key.match(RE_KEY_PATTERN)) {
|
|
100
|
-
debug('cache key not fully formatted, skip "%s"', _key)
|
|
101
|
-
return run()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function run() {
|
|
105
|
-
fn.apply(self, args)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function done(err, reply) {
|
|
109
|
-
return callback.call(self, err, reply)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
cached.get(_key, function(err, reply) {
|
|
113
|
-
if (err) {
|
|
114
|
-
if (!cached.silent) {
|
|
115
|
-
// ignore cache error unless not silent
|
|
116
|
-
return done(err)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// cache found
|
|
120
|
-
if (reply !== undefined) {
|
|
121
|
-
return done(null, reply)
|
|
122
|
-
}
|
|
123
|
-
// make sure we save cache after callback
|
|
124
|
-
args[args.length - 1] = function(err, result) {
|
|
125
|
-
function _done(){
|
|
126
|
-
callback.call(self, err, result)
|
|
127
|
-
}
|
|
128
|
-
if (err || result === undefined) {
|
|
129
|
-
return _done()
|
|
130
|
-
}
|
|
131
|
-
// save the cache
|
|
132
|
-
cached.set(_key, result, ttl, _done)
|
|
133
|
-
}
|
|
134
|
-
run()
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get function's realname
|
|
141
|
-
*/
|
|
142
|
-
function getName(fn) {
|
|
143
|
-
return fn[_REALNAME] || fn.name
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* How to replace a cache key
|
|
148
|
-
*/
|
|
149
|
-
Cacheable.keyReplacer = function(key, fn, args) {
|
|
150
|
-
var self = this
|
|
151
|
-
var data = {
|
|
152
|
-
_fn_: fn && getName(fn),
|
|
153
|
-
_model_: self[_REALNAME]
|
|
154
|
-
}
|
|
155
|
-
return keyf(key, data).call(self, args)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
Cacheable._REALNAME = _REALNAME
|
|
159
|
-
|
|
160
|
-
module.exports = Cacheable
|
|
161
|
-
|
|
162
|
-
require('./registry')
|
package/lib/registry.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
// Global cache for constructor classes
|
|
2
|
-
var constructors = {}
|
|
3
|
-
var RE_JSON_TIME = /^[0-9\-]+T[0-9\:\.]+Z$/
|
|
4
|
-
|
|
5
|
-
var Cacheable = require('./cacheable')
|
|
6
|
-
|
|
7
|
-
var _REALNAME = Cacheable._REALNAME
|
|
8
|
-
|
|
9
|
-
// default keys for wrapping model methods
|
|
10
|
-
var DEFAULT_KEY = '{_model_}:{_fn_}'
|
|
11
|
-
var DEFAULT_PROTO_KEY = '{_model_}:{id}:{_fn_}'
|
|
12
|
-
|
|
13
|
-
function enableCache(cached) {
|
|
14
|
-
function enableInstCache(method, key, ttl) {
|
|
15
|
-
var cls = this
|
|
16
|
-
var fn = isValidFunc(cls.prototype[method], method)
|
|
17
|
-
key = key || DEFAULT_PROTO_KEY.replace('{_fn_}', method)
|
|
18
|
-
hiddenProperty(fn, _REALNAME, fn.name || method)
|
|
19
|
-
cls.addCacheKey(key)
|
|
20
|
-
cls.prototype['fresh_' + method] = fn
|
|
21
|
-
cls.prototype[method] = cached.wrap(fn, key, ttl)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return function _enableCache(method, key, ttl) {
|
|
25
|
-
var cls = this, fn
|
|
26
|
-
// can pass ttl as the second argument
|
|
27
|
-
if ('number' === typeof key) {
|
|
28
|
-
ttl = key
|
|
29
|
-
key = null
|
|
30
|
-
}
|
|
31
|
-
if (method[0] === '.') {
|
|
32
|
-
return enableInstCache.call(this, method.slice(1), key, ttl)
|
|
33
|
-
}
|
|
34
|
-
fn = isValidFunc(cls[method], method)
|
|
35
|
-
key = key || DEFAULT_KEY.replace('{_fn_}', method)
|
|
36
|
-
hiddenProperty(fn, _REALNAME, fn.name || method)
|
|
37
|
-
cls['fresh_' + method] = fn
|
|
38
|
-
cls[method] = cached.wrap(fn, key, ttl, cls)
|
|
39
|
-
cls.addCacheKey(key, true)
|
|
40
|
-
return cls[method]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function clearCache(cached) {
|
|
45
|
-
return function _clearCache(callback) {
|
|
46
|
-
cached.del(this._cacheKeys, callback)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function addCacheKey(key, isClassMethod) {
|
|
51
|
-
if (isClassMethod) {
|
|
52
|
-
this.classCacheKeys.push(key)
|
|
53
|
-
} else {
|
|
54
|
-
this.itemCacheKeys.push(key)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Revive object from a json string
|
|
60
|
-
*/
|
|
61
|
-
function reviver(k, v) {
|
|
62
|
-
if ('object' == typeof v && v !== null && !Array.isArray(v)) {
|
|
63
|
-
var cls = v[_REALNAME]
|
|
64
|
-
if (cls) {
|
|
65
|
-
if (!(cls in constructors)) {
|
|
66
|
-
log('Constructor for %s doesn\'t exist anymore.', cls)
|
|
67
|
-
// return a undefined, mean this object is not available anymore
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
cls = constructors[cls]
|
|
71
|
-
delete v[_REALNAME]
|
|
72
|
-
v = new cls(v)
|
|
73
|
-
if (v._unpickle) {
|
|
74
|
-
v._unpickle()
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return v
|
|
78
|
-
}
|
|
79
|
-
if ('string' === typeof v && RE_JSON_TIME.test(v)) {
|
|
80
|
-
// revive a date object
|
|
81
|
-
v = new Date(v)
|
|
82
|
-
}
|
|
83
|
-
return v
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function unpickle(value) {
|
|
87
|
-
return JSON.parse(value, reviver)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Cacheable.prototype._initClassHelper = function() {
|
|
92
|
-
this.helpers.enableCache = enableCache(this)
|
|
93
|
-
this.helpers.clearCache = clearCache(this)
|
|
94
|
-
this.helpers.addCacheKey = addCacheKey
|
|
95
|
-
this.unpickle = unpickle
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Register constructor class for unpickle
|
|
100
|
-
*/
|
|
101
|
-
Cacheable.prototype.register = function(cls, modelName) {
|
|
102
|
-
modelName = modelName || cls.modelName || cls.name
|
|
103
|
-
hiddenProperty(cls, _REALNAME, modelName)
|
|
104
|
-
hiddenProperty(cls.prototype, _REALNAME, modelName)
|
|
105
|
-
if (cls[_REALNAME] in constructors) {
|
|
106
|
-
throw new Error('Class "' + cls[_REALNAME] + '" already defined')
|
|
107
|
-
}
|
|
108
|
-
if (!this.helpers.enableCache) {
|
|
109
|
-
this._initClassHelper()
|
|
110
|
-
}
|
|
111
|
-
this._extend(cls)
|
|
112
|
-
constructors[cls[_REALNAME]] = cls
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Extend Model with methods to enable and clear cache
|
|
118
|
-
*/
|
|
119
|
-
Cacheable.prototype._extend = function extendClass(cls) {
|
|
120
|
-
var cached = this, proto = cls.prototype
|
|
121
|
-
cls.classCacheKeys = []
|
|
122
|
-
// default item related cache
|
|
123
|
-
cls.itemCacheKeys = ['{_model_}:{id}']
|
|
124
|
-
// instance cache keys
|
|
125
|
-
Object.defineProperty(proto, '_cacheKeys', {
|
|
126
|
-
get: function() {
|
|
127
|
-
var self = this
|
|
128
|
-
return cls.itemCacheKeys.map(function(item) {
|
|
129
|
-
return cached._applykey(self, item)
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
cls.enableCache = cached.helpers.enableCache
|
|
134
|
-
cls.addCacheKey = cached.helpers.addCacheKey
|
|
135
|
-
proto._clearCache = cached.helpers.clearCache
|
|
136
|
-
|
|
137
|
-
if ('function' != typeof proto.toJSON) {
|
|
138
|
-
throw new Error('Cache-able class must have instance method .toJSON')
|
|
139
|
-
}
|
|
140
|
-
proto._toJSON = proto.toJSON
|
|
141
|
-
proto.toJSON = function toJSON() {
|
|
142
|
-
var obj = this._toJSON()
|
|
143
|
-
obj[_REALNAME] = cls[_REALNAME]
|
|
144
|
-
return obj
|
|
145
|
-
}
|
|
146
|
-
return cls
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
function hiddenProperty(where, property, value) {
|
|
151
|
-
Object.defineProperty(where, property, { value: value });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function isValidFunc(fn, method) {
|
|
155
|
-
if (!fn) {
|
|
156
|
-
throw new Error('method "' + method + '" not defined')
|
|
157
|
-
}
|
|
158
|
-
if ('function' !== typeof fn) {
|
|
159
|
-
throw new Error('method "' + method + '" is not a function')
|
|
160
|
-
}
|
|
161
|
-
return fn
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
package/test/cacheable.test.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
var should = require('should')
|
|
2
|
-
var LRU = require('lru-cache')
|
|
3
|
-
|
|
4
|
-
var Cacheable = require('../')
|
|
5
|
-
|
|
6
|
-
describe('Public API', function() {
|
|
7
|
-
|
|
8
|
-
var client, cache
|
|
9
|
-
|
|
10
|
-
describe('initialize', function() {
|
|
11
|
-
before(function() {
|
|
12
|
-
client = LRU()
|
|
13
|
-
cache = new Cacheable({ client: client })
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('new and no new', function() {
|
|
17
|
-
var cache2 = Cacheable({ client: client })
|
|
18
|
-
cache.should.be.instanceOf(Cacheable)
|
|
19
|
-
cache2.should.be.instanceOf(Cacheable)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('default options', function() {
|
|
23
|
-
cache.silent.should.equal(true)
|
|
24
|
-
cache.prefix.should.equal('cached:')
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('wrap', function() {
|
|
29
|
-
var reach = 0
|
|
30
|
-
|
|
31
|
-
function foo(callback) {
|
|
32
|
-
reach += 1
|
|
33
|
-
return callback(null, 1)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
beforeEach(function() {
|
|
37
|
-
reach = 0
|
|
38
|
-
client.reset()
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('fn with only callback', function(done) {
|
|
42
|
-
var fn = cache.wrap(foo, 'foo')
|
|
43
|
-
fn(function(err, result) {
|
|
44
|
-
result.should.equal(1)
|
|
45
|
-
fn(function(err, result) {
|
|
46
|
-
result.should.equal(1)
|
|
47
|
-
// if cache is used, `reach` will not accumulate
|
|
48
|
-
reach.should.equal(1)
|
|
49
|
-
done()
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('should have default key', function(done) {
|
|
55
|
-
var fn = cache.wrap(function foo(arg, callback) {
|
|
56
|
-
reach += arg.a
|
|
57
|
-
callback(null, 1)
|
|
58
|
-
})
|
|
59
|
-
var arg = { a: 1 }
|
|
60
|
-
fn(arg, function(err, result) {
|
|
61
|
-
result.should.equal(1)
|
|
62
|
-
// should've wrapped with default cache key: '{_fn_}:%j{0}'
|
|
63
|
-
should.equal(cache.get('foo:{"a":1}'), result)
|
|
64
|
-
fn(arg, function(err, result) {
|
|
65
|
-
result.should.equal(1)
|
|
66
|
-
reach.should.equal(1)
|
|
67
|
-
done()
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
describe('key replace', function() {
|
|
75
|
-
|
|
76
|
-
it('should handle positional', function(done) {
|
|
77
|
-
var fn = cache.wrap(foo, 'foor-{0}-{1}')
|
|
78
|
-
var reach = 0
|
|
79
|
-
|
|
80
|
-
fn(2, 5, function(err, result) {
|
|
81
|
-
reach.should.equal(7)
|
|
82
|
-
result.should.equal(3)
|
|
83
|
-
should.equal(cache.get('foor-2-5'), result)
|
|
84
|
-
fn(2, 5, function(err, result) {
|
|
85
|
-
reach.should.equal(7)
|
|
86
|
-
result.should.equal(3)
|
|
87
|
-
done()
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
function foo(arg1, arg2, callback) {
|
|
92
|
-
reach += arg1 + arg2
|
|
93
|
-
return callback(null, 3)
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('should handle context', function(done) {
|
|
98
|
-
var ctx = { hello: 'abc', again: 'def' }
|
|
99
|
-
var fn = cache.wrap(foo, 'contexted-{hello}-{this.hello}-{this.again}', null, ctx)
|
|
100
|
-
var reach = ''
|
|
101
|
-
|
|
102
|
-
fn(1, 2, function(err, result) {
|
|
103
|
-
reach.should.equal('abc')
|
|
104
|
-
result.should.equal('0')
|
|
105
|
-
should.equal(cache.get('contexted-abc-abc-def'), result)
|
|
106
|
-
fn(1, 2, function(err, result) {
|
|
107
|
-
reach.should.equal('abc')
|
|
108
|
-
result.should.equal('0')
|
|
109
|
-
done()
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
function foo(arg1, arg2, callback) {
|
|
114
|
-
reach += this.hello
|
|
115
|
-
return callback(null, '0')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('should handle context', function() {
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
describe('Registry', function() {
|
|
128
|
-
|
|
129
|
-
})
|
package/test/mocha.opts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
--timeout 200
|