├── .gitignore
├── .npmignore
├── LICENSE.md
├── README.md
├── commands
├── ConfigGenerator.js
├── TableGenerator.js
└── templates
│ ├── config.mustache
│ └── table.mustache
├── credits.md
├── package-lock.json
├── package.json
├── providers
├── CacheProvider.js
└── CommandsProvider.js
├── src
├── Events
│ ├── CacheHit.js
│ ├── CacheMissed.js
│ ├── KeyForgotten.js
│ └── KeyWritten.js
├── Stores
│ ├── CacheManager.js
│ ├── DatabaseStore.js
│ ├── NullStore.js
│ ├── ObjectStore.js
│ ├── RedisStore.js
│ ├── RedisTaggedCache.js
│ ├── Repository.js
│ ├── TagSet.js
│ ├── TaggableStore.js
│ └── TaggedCache.js
└── Util
│ └── index.js
└── test
├── DatabaseStore.spec.js
├── NullStore.spec.js
├── ObjectStore.spec.js
├── RedisStore.spec.js
├── RedisTaggedCache.spec.js
├── Repository.spec.js
└── TestHelpers.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | test/mydb.sqlite
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | test
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Hany El Nokaly < hany.elnokaly@gmail.com >
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AdonisCache
2 |
3 | This is a cache service provider for AdonisJS framework
4 |
5 | - [Installation](#installation)
6 | - [Configuration](#configuration)
7 | - [Driver Prerequisites](#driver-prerequisites)
8 | - [Cache Usage](#cache-usage)
9 | - [Obtaining A Cache Instance](#obtaining-a-cache-instance)
10 | - [Retrieving Items From The Cache](#retrieving-items-from-the-cache)
11 | - [Storing Items In The Cache](#storing-items-in-the-cache)
12 | - [Removing Items From The Cache](#removing-items-from-the-cache)
13 | - [Cache Tags](#cache-tags)
14 | - [Storing Tagged Cache Items](#storing-tagged-cache-items)
15 | - [Accessing Tagged Cache Items](#accessing-tagged-cache-items)
16 | - [Removing Tagged Cache Items](#removing-tagged-cache-items)
17 | - [Events](#events)
18 |
19 |
20 | ## Installation
21 |
22 | ```js
23 | npm i adonis-cache --save
24 | ```
25 |
26 | After installation, you need to register the providers inside `start/app.js` file.
27 |
28 | ##### start/app.js
29 | ```javascript
30 | const providers = [
31 | ...,
32 | 'adonis-cache/providers/CacheProvider'
33 | ]
34 | ```
35 |
36 | Also, for registering commands.
37 |
38 | ##### start/app.js
39 | ```javascript
40 | const aceProviders = [
41 | ...,
42 | 'adonis-cache/providers/CommandsProvider'
43 | ]
44 | ```
45 |
46 | Also, it is a good practice to setup an alias to avoid typing the complete namespace.
47 |
48 | ##### start/app.js
49 | ```javascript
50 | const aliases = {
51 | ...,
52 | Cache: 'Adonis/Addons/Cache'
53 | }
54 | ```
55 |
56 | Then, for generating a config file.
57 | ```bash
58 | adonis cache:config
59 | ```
60 |
61 |
62 | ## Configuration
63 |
64 | AdonisCache provides an expressive, unified API for various caching backends. The cache configuration is located at `config/cache.js`. In this file you may specify which cache driver you would like used by default throughout your application. AdonisCache supports popular caching backends like [Redis](http://redis.io) out of the box.
65 |
66 | The cache configuration file also contains various other options, which are documented within the file, so make sure to read over these options. By default, AdonisCache is configured to use the `object` cache driver, which stores cached objects in plain JavaScript object (use only for development). For larger applications, it is recommended that you use a more robust driver such as Redis. You may even configure multiple cache configurations for the same driver.
67 |
68 |
69 | ### Driver Prerequisites
70 |
71 | #### Database
72 |
73 | When using the `database` cache driver, you will need to setup a table to contain the cache items. You'll find an example `Schema` declaration for the table below:
74 | ```javascript
75 | this.create('cache', (table) => {
76 | table.string('key').unique()
77 | table.text('value')
78 | table.integer('expiration')
79 | })
80 | ```
81 |
82 | > {tip} You may also use the `adonis cache:table` Ace command to generate a migration with the proper schema.
83 |
84 | #### Redis
85 |
86 | Before using a Redis cache, you will need to have the Redis provider installed.
87 |
88 | For more information on configuring Redis, consult its [AdonisJs documentation page](http://adonisjs.com/docs/redis).
89 |
90 |
91 | ## Cache Usage
92 |
93 |
94 | ### Obtaining A Cache Instance
95 |
96 | ```javascript
97 | 'use strict'
98 |
99 | const Cache = use('Cache')
100 |
101 | class UserController {
102 |
103 | async index(request, response) {
104 | const value = await Cache.get('key')
105 |
106 | //
107 | }
108 | }
109 | ```
110 |
111 | #### Accessing Multiple Cache Stores
112 |
113 | You may access various cache stores via the `store` method. The key passed to the `store` method should correspond to one of the stores listed in the `stores` configuration object in your `cache` configuration file:
114 |
115 | ```javascript
116 | value = await Cache.store('database').get('foo')
117 |
118 | await Cache.store('redis').put('bar', 'baz', 10)
119 | ```
120 |
121 | ### Retrieving Items From The Cache
122 |
123 | The `get` method is used to retrieve items from the cache. If the item does not exist in the cache, `null` will be returned. If you wish, you may pass a second argument to the `get` method specifying the default value you wish to be returned if the item doesn't exist:
124 |
125 | ```javascript
126 | value = await Cache.get('key')
127 |
128 | value = await Cache.get('key', 'default')
129 | ```
130 |
131 | You may even pass a `Closure` as the default value. The result of the `Closure` will be returned if the specified item does not exist in the cache. Passing a Closure allows you to defer the retrieval of default values from a database or other external service:
132 |
133 | ```javascript
134 | value = await Cache.get('key', async () => {
135 | return await Database.table(...).where(...).first()
136 | })
137 | ```
138 |
139 | Retrieving multiple items:
140 |
141 | ```javascript
142 | values = await Cache.many(['key1', 'key2', 'key3'])
143 | // values = {
144 | // key1: value,
145 | // key2: value,
146 | // key3: value
147 | // }
148 | ```
149 |
150 | #### Checking For Item Existence
151 |
152 | The `has` method may be used to determine if an item exists in the cache:
153 |
154 | ```javascript
155 | if (await Cache.has('key')) {
156 | //
157 | }
158 | ```
159 |
160 | #### Incrementing / Decrementing Values
161 |
162 | The `increment` and `decrement` methods may be used to adjust the value of integer items in the cache. Both of these methods accept an optional second argument indicating the amount by which to increment or decrement the item's value:
163 |
164 | ```javascript
165 | await Cache.increment('key')
166 | await Cache.increment('key', amount)
167 | await Cache.decrement('key')
168 | await Cache.decrement('key', amount)
169 | ```
170 |
171 | #### Retrieve & Store
172 |
173 | Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the database and add them to the cache. You may do this using the `Cache.remember` method:
174 |
175 | ```javascript
176 | value = await Cache.remember('key', minutes, async () => {
177 | return await Database.table(...).where(...).first()
178 | })
179 | ```
180 |
181 | If the item does not exist in the cache, the `Closure` passed to the `remember` method will be executed and its result will be placed in the cache.
182 |
183 | #### Retrieve & Delete
184 |
185 | If you need to retrieve an item from the cache and then delete the item, you may use the `pull` method. Like the `get` method, `null` will be returned if the item does not exist in the cache:
186 |
187 | ```javascript
188 | value = await Cache.pull('key')
189 | ```
190 |
191 |
192 | ### Storing Items In The Cache
193 |
194 | You may use the `put` method on the `Cache` to store items in the cache. When you place an item in the cache, you need to specify the number of minutes for which the value should be cached:
195 |
196 | ```javascript
197 | await Cache.put('key', 'value', minutes)
198 | ```
199 |
200 | Instead of passing the number of minutes as an integer, you may also pass a `Date` instance representing the expiration time of the cached item:
201 |
202 | ```javascript
203 | const expiresAt = new Date(2016, 11, 1, 12, 0)
204 |
205 | await Cache.put('key', 'value', expiresAt)
206 | ```
207 |
208 | Storing multiple items:
209 |
210 | ```javascript
211 | const items = {
212 | key1: 'value1',
213 | key2: 'value2',
214 | key3: 'value3'
215 | }
216 |
217 | await Cache.putMany(items, minutes)
218 | ```
219 |
220 | #### Store If Not Present
221 |
222 | The `add` method will only add the item to the cache if it does not already exist in the cache store. The method will return `true` if the item is actually added to the cache. Otherwise, the method will return `false`:
223 |
224 | ```javascript
225 | await Cache.add('key', 'value', minutes)
226 | ```
227 |
228 | #### Storing Items Forever
229 |
230 | The `forever` method may be used to store an item in the cache permanently. Since these items will not expire, they must be manually removed from the cache using the `forget` method:
231 |
232 | ```javascript
233 | await Cache.forever('key', 'value')
234 | ```
235 |
236 |
237 | ### Removing Items From The Cache
238 |
239 | You may remove items from the cache using the `forget` method:
240 |
241 | ```javascript
242 | await Cache.forget('key')
243 | ```
244 |
245 | You may clear the entire cache using the `flush` method:
246 |
247 | ```javascript
248 | await Cache.flush()
249 | ```
250 |
251 | > {note} Flushing the cache does not respect the cache prefix and will remove all entries from the cache. Consider this carefully when clearing a cache which is shared by other applications.
252 |
253 |
254 | ## Cache Tags
255 |
256 | > {note} Cache tags are not supported when using the `database` cache driver.
257 |
258 |
259 | ### Storing Tagged Cache Items
260 |
261 | Cache tags allow you to tag related items in the cache and then flush all cached values that have been assigned a given tag. You may access a tagged cache by passing in an ordered array of tag names. For example, let's access a tagged cache and `put` value in the cache:
262 |
263 | ```javascript
264 | await Cache.tags(['people', 'artists']).put('John', john, minutes)
265 |
266 | await Cache.tags(['people', 'authors']).put('Anne', anne, minutes)
267 | ```
268 |
269 |
270 | ### Accessing Tagged Cache Items
271 |
272 | To retrieve a tagged cache item, pass the same ordered list of tags to the `tags` method and then call the `get` method with the key you wish to retrieve:
273 |
274 | ```javascript
275 | const john = await Cache.tags(['people', 'artists']).get('John')
276 |
277 | const anne = await Cache.tags(['people', 'authors']).get('Anne')
278 | ```
279 |
280 |
281 | ### Removing Tagged Cache Items
282 |
283 | You may flush all items that are assigned a tag or list of tags. For example, this statement would remove all caches tagged with either `people`, `authors`, or both. So, both `Anne` and `John` would be removed from the cache:
284 |
285 | ```javascript
286 | await Cache.tags(['people', 'authors']).flush()
287 | ```
288 |
289 | In contrast, this statement would remove only caches tagged with `authors`, so `Anne` would be removed, but not `John`:
290 |
291 | ```javascript
292 | await Cache.tags('authors').flush()
293 | ```
294 |
295 |
296 | ## Events
297 |
298 | To execute code on every cache operation, you may listen for the [events](http://adonisjs.com/docs/events) fired by the cache. Typically, you should place these event listeners within your `start/events.js`:
299 |
300 | ```
301 | Cache.hit
302 | Cache.missed
303 | Cache.keyForgotten
304 | Cache.keyWritten
305 | ```
306 |
--------------------------------------------------------------------------------
/commands/ConfigGenerator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | const path = require('path')
13 | const { Command } = require('@adonisjs/ace')
14 |
15 | /**
16 | * Command to generate a cache config file
17 | *
18 | * @class ConfigGenerator
19 | * @constructor
20 | */
21 | class ConfigGenerator extends Command {
22 | constructor (Helpers) {
23 | super()
24 | this.Helpers = Helpers
25 | }
26 |
27 | /**
28 | * IoC container injections
29 | *
30 | * @method inject
31 | *
32 | * @return {Array}
33 | */
34 | static get inject () {
35 | return ['Adonis/Src/Helpers']
36 | }
37 |
38 | /**
39 | * The command signature
40 | *
41 | * @method signature
42 | *
43 | * @return {String}
44 | */
45 | static get signature () {
46 | return 'cache:config'
47 | }
48 |
49 | /**
50 | * The command description
51 | *
52 | * @method description
53 | *
54 | * @return {String}
55 | */
56 | static get description () {
57 | return 'Generate cache config file'
58 | }
59 |
60 | /**
61 | * Method called when command is executed
62 | *
63 | * @method handle
64 | *
65 | * @param {Object} options
66 | * @return {void}
67 | */
68 | async handle (options) {
69 | /**
70 | * Reading template as a string form the mustache file
71 | */
72 | const template = await this.readFile(path.join(__dirname, './templates/config.mustache'), 'utf8')
73 |
74 | /**
75 | * Directory paths
76 | */
77 | const relativePath = path.join('config', 'cache.js')
78 | const configPath = path.join(this.Helpers.appRoot(), relativePath)
79 |
80 | /**
81 | * If command is not executed via command line, then return
82 | * the response
83 | */
84 | if (!this.viaAce) {
85 | return this.generateFile(configPath, template, {})
86 | }
87 |
88 | /**
89 | * Otherwise wrap in try/catch and show appropriate messages
90 | * to the end user.
91 | */
92 | try {
93 | await this.generateFile(configPath, template, {})
94 | this.completed('create', relativePath)
95 | } catch (error) {
96 | this.error(`${relativePath} cache config file already exists`)
97 | }
98 | }
99 | }
100 |
101 | module.exports = ConfigGenerator
102 |
--------------------------------------------------------------------------------
/commands/TableGenerator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | const path = require('path')
13 | const { Command } = require('@adonisjs/ace')
14 |
15 | /**
16 | * Command to generate a migration for the cache database table
17 | *
18 | * @class TableGenerator
19 | * @constructor
20 | */
21 | class TableGenerator extends Command {
22 | constructor(Helpers) {
23 | super()
24 | this.Helpers = Helpers
25 | }
26 |
27 | /**
28 | * IoC container injections
29 | *
30 | * @method inject
31 | *
32 | * @return {Array}
33 | */
34 | static get inject() {
35 | return ['Adonis/Src/Helpers']
36 | }
37 |
38 | /**
39 | * The command signature
40 | *
41 | * @method signature
42 | *
43 | * @return {String}
44 | */
45 | static get signature() {
46 | return 'cache:table'
47 | }
48 |
49 | /**
50 | * The command description
51 | *
52 | * @method description
53 | *
54 | * @return {String}
55 | */
56 | static get description() {
57 | return 'Generate a migration for the cache database table'
58 | }
59 |
60 | /**
61 | * Method called when command is executed
62 | *
63 | * @method handle
64 | *
65 | * @param {Object} options
66 | * @return {void}
67 | */
68 | async handle(options) {
69 | /**
70 | * Reading template as a string form the mustache file
71 | */
72 | const template = await this.readFile(path.join(__dirname, './templates/table.mustache'), 'utf8')
73 |
74 | /**
75 | * Directory paths
76 | */
77 | const relativePath = path.join('database/migrations', `${new Date().getTime()}_create_cache_table.js`)
78 | const fullPath = path.join(this.Helpers.appRoot(), relativePath)
79 |
80 | /**
81 | * If command is not executed via command line, then return
82 | * the response
83 | */
84 | if (!this.viaAce) {
85 | return this.generateFile(fullPath, template, {})
86 | }
87 |
88 | /**
89 | * Otherwise wrap in try/catch and show appropriate messages
90 | * to the end user.
91 | */
92 | try {
93 | await this.generateFile(fullPath, template, {})
94 | this.completed('create', relativePath)
95 | } catch (error) {
96 | this.error(`${relativePath} file already exists`)
97 | }
98 | }
99 | }
100 |
101 | module.exports = TableGenerator
102 |
--------------------------------------------------------------------------------
/commands/templates/config.mustache:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Env = use('Env')
4 | const Helpers = use('Helpers')
5 |
6 | module.exports = {
7 |
8 | /*
9 | |--------------------------------------------------------------------------
10 | | Default Cache Store
11 | |--------------------------------------------------------------------------
12 | |
13 | | This option controls the default cache store that gets used while
14 | | using this caching library. This store is used when another is
15 | | not explicitly specified when executing a given caching function.
16 | |
17 | */
18 |
19 | default: Env.get('CACHE_STORE', 'object'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Cache Stores
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may define all of the cache "stores" for your application as
27 | | well as their drivers. You may even define multiple stores for the
28 | | same cache driver to group types of items stored in your caches.
29 | |
30 | | Supported drivers: "object", "database", "redis"
31 | |
32 | | Hint: Use "null" driver for disabling caching
33 | | Warning: Use the "object" driver only for development, it does not have a garbage collector.
34 | |
35 | */
36 |
37 | stores: {
38 |
39 | object: {
40 | driver: 'object'
41 | },
42 |
43 | database: {
44 | driver: 'database',
45 | table: 'cache',
46 | connection: 'sqlite'
47 | },
48 |
49 | redis: {
50 | driver: 'redis',
51 | connection: 'local'
52 | },
53 |
54 | null: {
55 | driver: 'null'
56 | }
57 |
58 | },
59 |
60 | /*
61 | |--------------------------------------------------------------------------
62 | | Cache Key Prefix
63 | |--------------------------------------------------------------------------
64 | |
65 | | When utilizing a RAM based store, there might be other applications
66 | | utilizing the same cache. So, we'll specify a value to get prefixed
67 | | to all our keys so we can avoid collisions.
68 | |
69 | */
70 |
71 | prefix: 'adonis'
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/commands/templates/table.mustache:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Schema = use('Schema')
4 |
5 | class CreateCacheTable extends Schema {
6 |
7 | /**
8 | * Run the migrations.
9 | *
10 | * @return {void}
11 | */
12 | up () {
13 | this.create('cache', (table) => {
14 | table.string('key').unique()
15 | table.text('value')
16 | table.integer('expiration')
17 | })
18 | }
19 |
20 | /**
21 | * Reverse the migrations/
22 | *
23 | * @return {void}
24 | */
25 | down () {
26 | this.drop('cache')
27 | }
28 |
29 | }
30 |
31 | module.exports = CreateCacheTable
32 |
--------------------------------------------------------------------------------
/credits.md:
--------------------------------------------------------------------------------
1 | ### Laravel Framework
2 | ### This project is inspired and substantial portions of it are ported from the Laravel Framework
3 | ---
4 | The MIT License (MIT)
5 |
6 | Copyright (c) Taylor Otwell
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adonis-cache",
3 | "version": "0.3.4",
4 | "description": "Cache provider for AdonisJs framework",
5 | "scripts": {
6 | "test": "node node_modules/.bin/mocha",
7 | "lint": "standard src/**/*.js test/*.js providers/*.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/helnokaly/adonis-cache.git"
12 | },
13 | "keywords": [
14 | "adonisjs",
15 | "adonis",
16 | "cache",
17 | "caching"
18 | ],
19 | "author": "Hany El Nokaly",
20 | "license": "MIT",
21 | "homepage": "https://github.com/helnokaly/adonis-cache",
22 | "devDependencies": {
23 | "@adonisjs/ace": "^4.0.5",
24 | "@adonisjs/fold": "^4.0.2",
25 | "chai": "^4.1.2",
26 | "ioredis": "^3.2.2",
27 | "knex": "^0.16.3",
28 | "mocha": "^4.0.1",
29 | "sqlite3": "^4.0.6",
30 | "standard": "^10.0.3"
31 | },
32 | "dependencies": {
33 | "lodash": "^4.17.11"
34 | },
35 | "standard": {
36 | "globals": [
37 | "describe",
38 | "it"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/providers/CacheProvider.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | const { ServiceProvider } = require('@adonisjs/fold')
13 | const CacheManager = require('../src/Stores/CacheManager')
14 |
15 | class CacheProvider extends ServiceProvider {
16 | /**
17 | * Register all the required providers
18 | *
19 | * @method register
20 | *
21 | * @return {void}
22 | */
23 | register () {
24 | this.app.singleton('Adonis/Addons/Cache', (app) => {
25 | return new CacheManager(app)
26 | })
27 | }
28 | }
29 |
30 | module.exports = CacheProvider
31 |
--------------------------------------------------------------------------------
/providers/CommandsProvider.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | const { ServiceProvider } = require('@adonisjs/fold')
13 |
14 | class CommandsProvider extends ServiceProvider {
15 | /**
16 | * Register all the required providers
17 | *
18 | * @method register
19 | *
20 | * @return {void}
21 | */
22 | register () {
23 | this.app.bind('Adonis/Commands/Cache:Config', () => require('../commands/ConfigGenerator'))
24 | this.app.bind('Adonis/Commands/Cache:Table', () => require('../commands/TableGenerator'))
25 | }
26 |
27 | /**
28 | * On boot
29 | *
30 | * @method boot
31 | *
32 | * @return {void}
33 | */
34 | boot () {
35 | /**
36 | * Register command with ace.
37 | */
38 | const ace = require('@adonisjs/ace')
39 | ace.addCommand('Adonis/Commands/Cache:Config')
40 | ace.addCommand('Adonis/Commands/Cache:Table')
41 | }
42 | }
43 |
44 | module.exports = CommandsProvider
45 |
--------------------------------------------------------------------------------
/src/Events/CacheHit.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class CacheHit {
13 | /**
14 | * Create a new event instance.
15 | *
16 | * @param {string} key The key that was hit
17 | * @param {mixed} value The value that was retrieved
18 | * @param {array} tags The tags that were assigned to the key
19 | * @returns {void}
20 | */
21 | constructor (key, value, tags = []) {
22 | this.key = key
23 | this.tags = tags
24 | this.value = value
25 | }
26 | }
27 |
28 | CacheHit.EVENT = 'Cache.hit'
29 |
30 | module.exports = CacheHit
31 |
--------------------------------------------------------------------------------
/src/Events/CacheMissed.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class CacheMissed {
13 | /**
14 | * Create a new event instance.
15 | *
16 | * @param {string} key The key that was missed
17 | * @param {array} tags The tags that were assigned to the key
18 | * @returns {void}
19 | */
20 | constructor (key, tags = []) {
21 | this.key = key
22 | this.tags = tags
23 | }
24 | }
25 |
26 | CacheMissed.EVENT = 'Cache.missed'
27 |
28 | module.exports = CacheMissed
29 |
--------------------------------------------------------------------------------
/src/Events/KeyForgotten.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class KeyForgotten {
13 | /**
14 | * Create a new event instance.
15 | *
16 | * @param {string} key The key that was forgotten
17 | * @param {array} tags The tags that were assigned to the key
18 | * @returns {void}
19 | */
20 | constructor (key, tags = []) {
21 | this.key = key
22 | this.tags = tags
23 | }
24 | }
25 |
26 | KeyForgotten.EVENT = 'Cache.keyForgotten'
27 |
28 | module.exports = KeyForgotten
29 |
--------------------------------------------------------------------------------
/src/Events/KeyWritten.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | class KeyWritten {
13 | /**
14 | * Create a new event instance.
15 | *
16 | * @param {string} key The key that was written
17 | * @param {mixed} value The value that was written
18 | * @param {int} minutes The number of minutes the key should be valid
19 | * @param {array} tags The tags that were assigned to the key
20 | * @returns {void}
21 | */
22 | constructor (key, value, minutes, tags = []) {
23 | this.key = key
24 | this.tags = tags
25 | this.value = value
26 | this.minutes = minutes
27 | }
28 | }
29 |
30 | KeyWritten.EVENT = 'Cache.keyWritten'
31 |
32 | module.exports = KeyWritten
33 |
--------------------------------------------------------------------------------
/src/Stores/CacheManager.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | const ObjectStore = require('./ObjectStore')
13 | const RedisStore = require('./RedisStore')
14 | const NullStore = require('./NullStore')
15 | const DatabaseStore = require('./DatabaseStore')
16 | const Repository = require('./Repository')
17 |
18 | class CacheManager {
19 | constructor (app) {
20 | this._app = app // The application instance
21 | this._stores = [] // The array of resolved cache stores
22 | this._customCreators = [] // The registered custom driver creators
23 |
24 | return new Proxy(this, {
25 | get: function (target, name) {
26 | if (target[name] !== undefined) {
27 | return target[name]
28 | }
29 | // Dynamically call the default driver instance
30 | const store = target.store()
31 | if (typeof store[name] === 'function') {
32 | return store[name].bind(store)
33 | }
34 | }
35 | })
36 | }
37 |
38 | /**
39 | * Get a cache store instance by name.
40 | *
41 | * @param {string|null} name
42 | * @return {mixed}
43 | */
44 | store (name = null) {
45 | name = name || this.getDefaultDriver()
46 | this._stores[name] = this._get(name)
47 | return this._stores[name]
48 | }
49 |
50 | /**
51 | * Get a cache driver instance.
52 | *
53 | * @param {string} driver
54 | * @return {mixed}
55 | */
56 | driver (driver = null) {
57 | return this._store(driver)
58 | }
59 |
60 | /**
61 | * Attempt to get the store from the local cache.
62 | *
63 | * @param {string} name
64 | * @return {Repository}
65 | * @private
66 | */
67 | _get (name) {
68 | return this._stores[name] != null ? this._stores[name] : this._resolve(name)
69 | }
70 |
71 | /**
72 | * Resolve the given store.
73 | *
74 | * @param {string} name
75 | * @return {Repository}
76 | *
77 | * @throws {InvalidArgumentException}
78 | * @private
79 | */
80 | _resolve (name) {
81 | const config = this._getConfig(name)
82 |
83 | if (config == null) {
84 | throw new Error(`InvalidArgumentException: Cache store [${name}] is not defined.`)
85 | }
86 |
87 | if (this._customCreators[config['driver']] != null) {
88 | return this._callCustomCreator(config)
89 | } else {
90 | const driveName = config['driver'].charAt(0).toUpperCase() + config['driver'].substr(1).toLowerCase()
91 | const driverMethod = '_create' + driveName + 'Driver'
92 |
93 | if (typeof this[driverMethod] === 'function') {
94 | return this[driverMethod](config)
95 | } else {
96 | throw new Error(`InvalidArgumentException: Driver [${config['driver']}] is not supported.`)
97 | }
98 | }
99 | }
100 |
101 | /**
102 | * Call a custom driver creator.
103 | *
104 | * @param {object} config
105 | * @return {mixed}
106 | * @private
107 | */
108 | _callCustomCreator (config) {
109 | return this._customCreators[config['driver']](this._app, config)
110 | }
111 |
112 | /**
113 | * Create an instance of the Null cache driver.
114 | *
115 | * @return {Repository}
116 | * @private
117 | */
118 | _createNullDriver () {
119 | return this.repository(new NullStore())
120 | }
121 |
122 | /**
123 | * Create an instance of the object cache driver.
124 | *
125 | * @return {Repository}
126 | * @private
127 | */
128 | _createObjectDriver () {
129 | return this.repository(new ObjectStore())
130 | }
131 |
132 | /**
133 | * Create an instance of the Redis cache driver.
134 | *
135 | * @param {object} config
136 | * @return {Repository}
137 | * @private
138 | */
139 | _createRedisDriver (config) {
140 | const redis = this._app.use('Adonis/Addons/Redis')
141 | const connection = config['connection'] ? config['connection'] : 'local'
142 | return this.repository(new RedisStore(redis, this._getPrefix(config), connection))
143 | }
144 |
145 | /**
146 | * Create an instance of the database cache driver.
147 | *
148 | * @param {object} config
149 | * @return {Repository}
150 | * @private
151 | */
152 | _createDatabaseDriver (config) {
153 | const connection = this._app.use('Adonis/Src/Database').connection(config['connection'])
154 |
155 | return this.repository(new DatabaseStore(connection, config['table'], this._getPrefix(config)))
156 | }
157 |
158 | /**
159 | * Create a new cache repository with the given implementation.
160 | *
161 | * @param {Store} store
162 | * @return {Repository}
163 | */
164 | repository (store) {
165 | const repository = new Repository(store)
166 |
167 | const Event = this._app.use('Adonis/Src/Event')
168 | if (Event != null) {
169 | repository.setEventDispatcher(Event)
170 | }
171 |
172 | return repository
173 | }
174 |
175 | /**
176 | * Get the cache prefix.
177 | *
178 | * @param {object} config
179 | * @return {string}
180 | * @private
181 | */
182 | _getPrefix (config) {
183 | return config['prefix'] ? config['prefix'] : this._app.use('Adonis/Src/Config').get('cache.prefix')
184 | }
185 |
186 | /**
187 | * Get the cache connection configuration.
188 | *
189 | * @param {string} name
190 | * @return {object}
191 | * @private
192 | */
193 | _getConfig (name) {
194 | return this._app.use('Adonis/Src/Config').get(`cache.stores.${name}`)
195 | }
196 |
197 | /**
198 | * Get the default cache driver name.
199 | *
200 | * @return {string}
201 | */
202 | getDefaultDriver () {
203 | return this._app.use('Adonis/Src/Config').get('cache.default')
204 | }
205 |
206 | /**
207 | * Set the default cache driver name.
208 | *
209 | * @param {string} name
210 | * @return {void}
211 | */
212 | setDefaultDriver (name) {
213 | // this._app.use('Adonis/Src/Config').get('cache.default') = name
214 | }
215 |
216 | /**
217 | * Register a custom driver creator Closure.
218 | *
219 | * @param {string} driver
220 | * @param {function} closure
221 | * @return {this}
222 | */
223 | extend (driver, closure) {
224 | // this._customCreators[driver] = closure.bindTo(this, this)
225 | this._customCreators[driver] = closure.bind(this)
226 | return this
227 | }
228 | }
229 |
230 | module.exports = CacheManager
231 |
--------------------------------------------------------------------------------
/src/Stores/DatabaseStore.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * adonis-cache
5 | *
6 | * (c) Hany El Nokaly
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | const Util = require('../Util')
13 |
14 | class DatabaseStore {
15 | constructor (connection, tableName, prefix = '') {
16 | this._connection = connection
17 | this._tableName = tableName
18 | this._prefix = prefix
19 |
20 | /**
21 | * Probability (parts per million) that garbage collection (GC) should be performed
22 | * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
23 | * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
24 | */
25 | this._gcProbability = 100;
26 | }
27 |
28 | /**
29 | * Return a new query builder instance with cache's table set
30 | *
31 | * @returns {mixed}
32 | * @private
33 | */
34 | _table () {
35 | return this._connection.table(this._tableName)
36 | }
37 |
38 | /**
39 | * Retrieve an item from the cache by key.
40 | *
41 | * @param {string} key
42 | * @return {Promise}
43 | */
44 | async get (key) {
45 | const cache = await this._table().where('key', this._prefix + key).first()
46 |
47 | if (cache === undefined) {
48 | return null
49 | }
50 |
51 | if (Date.now() / 1000 >= cache.expiration) {
52 | await this.forget(key)
53 | return null
54 | }
55 |
56 | return Util.deserialize(cache.value)
57 | }
58 |
59 | /**
60 | * Retrieve multiple items from the cache by key.
61 | *
62 | * Items not found in the cache will have a null value.
63 | *
64 | * @param {Array} keys
65 | * @return {Promise