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 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
- # Cacheable
1
+ [<img align="center" src="https://jaredwray.com/images/cacheable_white.svg" alt="keyv">](https://github.com/jaredwray/cacheable)
2
2
 
3
- Cache manager that doesn't suck.
3
+ # cacheable
4
4
 
5
- Make the result of you async functions cacheable, automatically pickle and unpickle the data.
6
- Manage all cache keys in one place, use a simple `._clearCache()` to purge cache.
5
+ > Simple Caching Engine using Keyv
7
6
 
7
+ [![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/master/graph/badge.svg?token=LDLaqe4PsI)](https://codecov.io/gh/jaredwray/cacheable)
8
+ [![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
9
+ [![npm](https://img.shields.io/npm/dm/cacheable.svg)](https://www.npmjs.com/package/cacheable)
10
+ [![npm](https://img.shields.io/npm/v/cacheable)](https://www.npmjs.com/package/cacheable)
8
11
 
9
- ## Usage
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
- ```javascript
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
- cached.set(key, value, callback)
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
- Wraping an async function:
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
- Manage cache for your models:
30
+ ## Basic Usage
40
31
 
41
32
  ```javascript
42
- function User(data) {
43
- this.attributes = data
44
- }
33
+ import { Cacheable } from 'cacheable';
45
34
 
46
- User.prototype.toJSON = function() {
47
- return this.attributes
48
- }
35
+ const cacheable = new Cacheable();
36
+ cacheable.set('key', 'value', 1000);
37
+ const value = cacheable.get('key');
38
+ ```
49
39
 
50
- // get user by id
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
- callback(err, user);
57
- }
42
+ ```javascript
43
+ import { Cacheable } from 'cacheable';
58
44
 
59
- User.prototype.getPostIds = function(start, limit, callback) {
60
- callback(null, [1,2,3...])
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
- ## API
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
- You have to `register` all model constructors, so when cache is hit, the cached manager would know
80
- how to restore the data as a proper JavaScript Object.
54
+ ## Storage Adapters and Keyv
81
55
 
82
- If your model constructor doesn't have a name, you can give a name as the second parameter,
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
- var Book = function() {
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
- Your class.prototype must have a `.toJSON` method, so the cache wrapper could know how to save it to cache.
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
- var item = new User(json)
102
- item._unpickle()
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
- When registered, the class will have a property `._cacheKeys` and an instance would have
108
- a method `._clearCache()`.
75
+ export class MyCache extends Cacheable {
76
+ constructor() {
77
+ super();
109
78
 
110
- ```javascript
111
- User.prototype.destroy = function(callback) {
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
- ### cached.wrap(fn, [key], [ttl])
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
- The parameter `key` is a pattern for formatting real cache keys.
86
+ To get statistics on your cache, you can do the following:
128
87
 
129
- The default `key` is:
130
-
131
- {_model_}:{_fn_}
88
+ ```javascript
89
+ import { Cacheable } from 'cacheable';
132
90
 
133
- `{_fn_}` is the name of the function `fn`. If not found, an error will throw.
134
- So you'd better alway name your functions, like this:
91
+ export class MyCache extends Cacheable {
92
+ constructor() {
93
+ super();
94
+ }
135
95
 
136
- ```javascript
137
- User.get = function get(id) {
138
- // ...
96
+ async getStats() {
97
+ return this.stats.getReport();
98
+ }
139
99
  }
140
100
  ```
141
101
 
142
- `{_model_}` equals to `{this.name}`, which is `this.modelName || this.name` in the scope when the function is called.
143
- For a class method, this usually means the name of a constructor.
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
- Numbers like `{0}` is indexes of arguments when the function is called.
146
- `%j{0}` mean the first argument value will be converted to json.
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
- ### cls.enableCache(methodName, [key], [ttl])
162
+ The following hooks are available for you to extend the functionality of `cacheable`:
150
163
 
151
- When a `cls` is registered, you can use `cls.enableCache` to enable cache for class/instance methods.
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
- If `methodName` starts with a dot `(.)`, it will be considered as an instance method, otherwise,
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
- User.enableCache('getAllIds', 'ids-{0.limit}-{0.offset}')
176
-
177
- User.enableCache('.getPostIds', 'posts-{0}-{1}')
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
- It is strongly recommended to use this approach to add cache, instead of directly call `cached.wrap`.
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
- ## License
195
+ ## API
188
196
 
189
- the MIT licence.
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.2.7",
4
- "description": "A cache wrapper with redis",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "./node_modules/.bin/mocha"
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
- "debug": "0.7.x",
11
- "keyf": "~0.0.1",
12
- "storeman": "~0.1.1"
18
+ "eventemitter3": "^5.0.1",
19
+ "hookified": "^0.7.0",
20
+ "keyv": "^5.0.1"
13
21
  },
14
- "devDependencies": {
15
- "mocha": "*",
16
- "should": "*",
17
- "lru-cache": "*",
18
- "istanbul": "*"
22
+ "xo": {
23
+ "rules": {
24
+ "unicorn/prefer-event-target": "off"
25
+ }
19
26
  },
20
- "repository": {
21
- "type": "git",
22
- "url": "git://github.com/ktmud/cached.git"
23
- },
24
- "keywords": [
25
- "redis",
26
- "redis-cache",
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
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src/**/*"]
4
+ }
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
@@ -1,3 +0,0 @@
1
- node_modules
2
- coverage.html
3
- coverage
package/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - 0.8
4
- - 0.10
package/Makefile DELETED
@@ -1,7 +0,0 @@
1
- .PHONY: test coverage
2
-
3
- test:
4
- @DEBUG= npm test
5
-
6
- coverage:
7
- @DEBUG= istanbul cover ./node_modules/.bin/_mocha && open ./coverage/lcov-report/index.html
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
-
@@ -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