| // After starting this example load http://localhost:8080 and hit refresh, you will notice that it loads the response from cache for the first 5 seconds and then reloads the cache | ||
| // Load modules | ||
| var Catbox = require('../'); | ||
| var Http = require('http'); | ||
| // Declare internals | ||
| var internals = {}; | ||
| internals.handler = function (req, res) { | ||
| internals.getResponse(function (err, item) { | ||
| if (err) { | ||
| res.writeHead(500); | ||
| res.end(); | ||
| } | ||
| else { | ||
| res.writeHead(200, {'Content-Type': 'text/plain'}); | ||
| res.end(item); | ||
| } | ||
| }); | ||
| }; | ||
| internals.getResponse = function (callback) { | ||
| var key = { | ||
| segment: 'example', | ||
| id: 'myExample' | ||
| }; | ||
| internals.client.get(key, function (err, cached) { | ||
| if (err) { | ||
| return callback(err); | ||
| } | ||
| else if (cached) { | ||
| return callback(null, 'From cache: ' + cached.item); | ||
| } | ||
| else { | ||
| internals.client.set(key, 'my example', 5000, function (error) { | ||
| callback(error, 'my example'); | ||
| }); | ||
| } | ||
| }); | ||
| }; | ||
| internals.startCache = function (callback) { | ||
| var options = { | ||
| engine: 'mongodb', | ||
| partition: 'examples' | ||
| }; | ||
| internals.client = new Catbox.Client(options); | ||
| internals.client.start(callback); | ||
| }; | ||
| internals.startServer = function () { | ||
| var server = Http.createServer(internals.handler); | ||
| server.listen(8080); | ||
| console.log('Server started at http://localhost:8080/'); | ||
| }; | ||
| internals.startCache(internals.startServer); |
| // After starting this example load http://localhost:8080 and hit refresh, you will notice that it loads the response from cache for the first 5 seconds and then reloads the cache. Look at the console to see it setting and getting items from cache. | ||
| // Load modules | ||
| var Catbox = require('../'); | ||
| var Http = require('http'); | ||
| // Declare internals | ||
| var internals = {}; | ||
| internals.handler = function (req, res) { | ||
| internals.getResponse(function (item) { | ||
| res.writeHead(200, {'Content-Type': 'text/plain'}); | ||
| res.end(item); | ||
| }); | ||
| }; | ||
| internals.getResponse = function (callback) { | ||
| var logFunc = function (item) { | ||
| console.log(item); | ||
| }; | ||
| var generateFunc = function (next) { | ||
| next(null, 'my example'); | ||
| }; | ||
| internals.policy.getOrGenerate('myExample', logFunc, generateFunc, callback); | ||
| }; | ||
| internals.startCache = function (callback) { | ||
| var clientOptions = { | ||
| engine: 'redis', | ||
| partition: 'examples' // For redis this will store items under keys that start with examples: | ||
| }; | ||
| var policyOptions = { | ||
| mode: 'server', | ||
| expiresIn: 5000, | ||
| segment: 'example' | ||
| }; | ||
| var client = new Catbox.Client(clientOptions); | ||
| client.start(function () { | ||
| internals.policy = new Catbox.Policy(policyOptions, client); | ||
| callback(); | ||
| }); | ||
| }; | ||
| internals.startServer = function () { | ||
| var server = Http.createServer(internals.handler); | ||
| server.listen(8080); | ||
| console.log('Server started at http://localhost:8080/'); | ||
| }; | ||
| internals.startCache(internals.startServer); |
| // After starting this example load http://localhost:8080 and hit refresh, you will notice that it loads the response from cache for the first 5 seconds and then reloads the cache | ||
| // Load modules | ||
| var Catbox = require('../'); | ||
| var Http = require('http'); | ||
| // Declare internals | ||
| var internals = {}; | ||
| internals.handler = function (req, res) { | ||
| internals.getResponse(function (err, item) { | ||
| if (err) { | ||
| res.writeHead(500); | ||
| res.end(); | ||
| } | ||
| else { | ||
| res.writeHead(200, {'Content-Type': 'text/plain'}); | ||
| res.end(item); | ||
| } | ||
| }); | ||
| }; | ||
| internals.getResponse = function (callback) { | ||
| var key = { | ||
| segment: 'example', | ||
| id: 'myExample' | ||
| }; | ||
| internals.client.get(key, function (err, cached) { | ||
| if (err) { | ||
| return callback(err); | ||
| } | ||
| else if (cached) { | ||
| return callback(null, 'From cache: ' + cached.item); | ||
| } | ||
| else { | ||
| internals.client.set(key, 'my example', 5000, function (error) { | ||
| callback(error, 'my example'); | ||
| }); | ||
| } | ||
| }); | ||
| }; | ||
| internals.startCache = function (callback) { | ||
| var options = { | ||
| engine: 'redis', | ||
| partition: 'examples' // For redis this will store items under keys that start with examples: | ||
| }; | ||
| internals.client = new Catbox.Client(options); | ||
| internals.client.start(callback); | ||
| }; | ||
| internals.startServer = function () { | ||
| var server = Http.createServer(internals.handler); | ||
| server.listen(8080); | ||
| console.log('Server started at http://localhost:8080/'); | ||
| }; | ||
| internals.startCache(internals.startServer); |
| // Load modules | ||
| var libPath = process.env.TEST_COV ? '../../lib-cov/' : '../../lib/'; | ||
| var Catbox = require(libPath); | ||
| // Declare internals | ||
| var internals = {}; | ||
| module.exports = internals.Server = function (settings) { | ||
| this.client = new Catbox.Client(settings); | ||
| this._routes = {}; | ||
| this.helpers = {}; | ||
| }; | ||
| internals.Server.prototype.addRoute = function (path, generator, policyOptions) { | ||
| this._routes[path] = { | ||
| generator: this._generatorWrapper(generator), | ||
| policy: new Catbox.Policy(policyOptions, this.client) | ||
| }; | ||
| }; | ||
| internals.Server.prototype._generatorWrapper = function (generator) { | ||
| var generatedValue = generator(); | ||
| var error = null; | ||
| var result = null; | ||
| if (generatedValue instanceof Error) { | ||
| error = generatedValue; | ||
| } | ||
| else { | ||
| result = generatedValue; | ||
| } | ||
| return function (callback) { | ||
| callback(error, result); | ||
| }; | ||
| }; | ||
| internals.Server.prototype.getResponse = function (path, callback) { | ||
| var route = this._routes[path]; | ||
| if (!route) { | ||
| return callback(new Error()); | ||
| } | ||
| route.policy.getOrGenerate(path, this._logger, route.generator, callback); | ||
| }; | ||
| internals.Server.prototype._logger = function (value) { | ||
| }; | ||
| internals.Server.prototype.addHelper = function (name, method, options) { | ||
| var self = this; | ||
| this.client.segment = options.segment || '#' + name; | ||
| var cache = new Catbox.Policy(options, this.client); | ||
| var helper = function (/* arguments, next */) { | ||
| // Prepare arguments | ||
| var args = arguments; | ||
| var lastArgPos = args.length - 1; | ||
| var next = args[lastArgPos]; | ||
| var generateFunc = function (callback) { | ||
| args[lastArgPos] = function (result) { | ||
| if (result instanceof Error) { | ||
| return callback(result); | ||
| } | ||
| return callback(null, result); | ||
| }; | ||
| method.apply(null, args); | ||
| }; | ||
| var key = (cache.isEnabled() ? internals.generateKey(args) : null); | ||
| cache.getOrGenerate(key, self._logger, generateFunc, function (response, cached) { | ||
| return next(response); | ||
| }); | ||
| }; | ||
| this.helpers[name] = helper; | ||
| }; | ||
| internals.generateKey = function (args) { | ||
| var key = ''; | ||
| for (var i = 0, il = args.length - 1; i < il; ++i) { // 'args.length - 1' to skip 'next' | ||
| var arg = args[i]; | ||
| if (typeof arg !== 'string' && | ||
| typeof arg !== 'number' && | ||
| typeof arg !== 'boolean') { | ||
| return null; | ||
| } | ||
| key += (i > 0 ? ':' : '') + encodeURIComponent(arg); | ||
| } | ||
| return key; | ||
| }; |
+12
-5
@@ -6,5 +6,4 @@ // Load modules | ||
| var Stale = require('./stale'); | ||
| var Redis = require('./redis'); | ||
| var Mongo = require('./mongo'); | ||
| var Memory = require('./memory'); | ||
| // Mongo and Redis are loaded below depending on the configuration | ||
@@ -41,6 +40,6 @@ | ||
| if (engine === 'redis') { | ||
| factory = Redis; | ||
| factory = require('./redis'); | ||
| } | ||
| else if (engine === 'mongodb') { | ||
| factory = Mongo; | ||
| factory = require('./mongo'); | ||
| } | ||
@@ -270,2 +269,3 @@ else if (engine === 'memory') { | ||
| * mode: 'server+client', | ||
| * privacy: 'public', | ||
| * expiresIn: 30000, | ||
@@ -292,2 +292,4 @@ * expiresAt: '13:00', | ||
| Hoek.assert(['none', 'client', 'server'].indexOf(mode) !== -1, 'Unknown cache mode: ' + mode); | ||
| if (mode !== 'none') { | ||
@@ -299,3 +301,3 @@ rule.mode[mode] = true; | ||
| if (Object.keys(rule.mode).length === 0) { | ||
| Hoek.assert(!config.expiresIn && !config.expiresAt && !config.staleIn && !config.staleTimeout, 'Cannot configure cache rules when mode is none'); | ||
| Hoek.assert(!Hoek.matchKeys(config, ['expiresIn','expiresAt','staleIn','staleTimeout','privacy','segment']).length, 'Cannot configure cache rules when mode is none'); | ||
| return rule; | ||
@@ -312,2 +314,3 @@ } | ||
| Hoek.assert(!config.staleTimeout || !config.expiresIn || config.staleTimeout < (config.expiresIn - config.staleIn), 'staleTimeout must be less than the delta between expiresIn and staleIn'); | ||
| Hoek.assert(!config.privacy || ['default', 'public', 'private'].indexOf(config.privacy) !== -1, 'Unknown privacy: ' + config.privacy); | ||
@@ -347,2 +350,6 @@ // Strict mode | ||
| // Privacy | ||
| rule.privacy = config.privacy || 'default'; | ||
| return rule; | ||
@@ -349,0 +356,0 @@ }; |
+2
-1
@@ -110,2 +110,3 @@ // Load modules | ||
| envelope.byteSize = internals.itemByteSize(value); | ||
| envelope.byteSize += internals.itemByteSize(key); | ||
@@ -186,5 +187,5 @@ if (self.byteSize + envelope.byteSize > this.settings.maxByteSize) { | ||
| var size = 8; // Initial object overhead | ||
| size += keys.length * 2; | ||
| for (var i = 0, il = keys.length; i < il; ++i) { | ||
| size += internals.itemByteSize(keys[i]); | ||
| size += internals.itemByteSize(object[keys[i]]); | ||
@@ -191,0 +192,0 @@ } |
+6
-7
| { | ||
| "name": "catbox", | ||
| "description": "Multi-strategy object caching service", | ||
| "version": "0.0.2", | ||
| "version": "0.1.0", | ||
| "author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)", | ||
@@ -22,10 +22,9 @@ "contributors":[ | ||
| "dependencies": { | ||
| "hoek": "0.0.x", | ||
| "redis": "0.8.x", | ||
| "mongodb": "1.1.x" | ||
| "hoek": "0.1.x" | ||
| }, | ||
| "devDependencies": { | ||
| "redis": "0.8.x", | ||
| "mongodb": "1.1.x", | ||
| "mocha": "1.x.x", | ||
| "chai": "1.2.x", | ||
| "hapi": "0.x.x" | ||
| "chai": "1.2.x" | ||
| }, | ||
@@ -41,2 +40,2 @@ "scripts": { | ||
| ] | ||
| } | ||
| } |
+85
-0
@@ -8,1 +8,86 @@ <a href="/walmartlabs/blammo"><img src="https://raw.github.com/walmartlabs/blammo/master/images/from.png" align="right" /></a> | ||
| The provided implementation includes support for Redis, MongoDB, and an experimental memory store (each must be manually installed and configured). _'Catbox'_ is useful for conveniently managing item cache rules and storage. | ||
| ### Installing the appropriate module dependency | ||
| The _'mongodb'_ and _'redis'_ modules are currently used only in a development environment. Therefore, to use _'Catbox'_ in production you will need to manually install the _'mongodb'_ or _'redis'_ modules. One way that these modules can be installed is by running the command `npm install mongodb` or `npm install redis`. Another way to install the modules is to add the appropriate one to the applications _'package.json'_ `dependencies` section and then by running `npm install`. | ||
| ### Client | ||
| Catbox has a _'Client'_ constructor that takes the following options. | ||
| * `engine` - the cache server implementation. Options are redis, mongodb, and memory. (required) | ||
| * `partition` - the partition name used to isolate the cached results across different servers. (required) | ||
| ##### Mongo Specific | ||
| * `host` - the cache server hostname. Defaults to _'127.0.0.1'_. | ||
| * `port` - the cache server port. Defaults to _'27017'_. | ||
| * `username` - when the mongo server requires authentication. Defaults to no authentication. | ||
| * `password` - used for authentication. | ||
| * `poolSize` - number of connections to leave open that can be used for catbox. Defaults to _'5'_. | ||
| ##### Redis Specific | ||
| * `host` - the cache server hostname. Defaults to _'127.0.0.1'_. | ||
| * `port` - the cache server port. Defaults to _'6479'_. | ||
| ##### Memory Specific | ||
| This is an experimental engine and should be avoided in production environments. | ||
| * `maxByteSize` - Sets an upper limit on the number of bytes that can be consumed by the total of everything cached in the memory engine. Once this limit is reached no more items will be added to the cache. Defaults to no limit. | ||
| #### Client Interface | ||
| After constructing a cache client the following methods are available. After each method description is the method signature. Please note that _'start'_ should be called before calling any of these methods. | ||
| * `start` - creates a connection to the cache server. (`function (callback)`) | ||
| * `stop` - terminates the connection to the cache server. (`function ()`) | ||
| * `get` - retrieve an item from the cache engine if its stored. (`function (key, callback)`) | ||
| * `set` - store an item in the cache at the given key for a specified length of time. (`function (key, value, ttl, callback)`) | ||
| * `drop` - remove the item from cache found at the given key. (`function (key, callback)`) | ||
| _'key'_ is an object with the following properties: | ||
| * `segment` - the parent category to store the item under | ||
| * `id` - should be unique across the segment, used to identify the stored item | ||
| ### Policy | ||
| Instead of dealing directly with the client interface using the _'Policy'_ interface is often preferred. It provides several helper methods like _'getOrGenerate'_ that will handle retrieving an item from cache when available or generating a new item and storing it in cache. _'Policy'_ is also useful for creating cache rules for different items and having them enforced. To construct a new _'Policy'_ the constructor takes the following parameters: | ||
| * `config` | ||
| * `mode` - determines if the item is cached on the server, client, or both. (required) | ||
| * `server+client` - Caches the item on the server and client | ||
| * `client` - Won't store the item on the server | ||
| * `server` - Caches the item on the server only | ||
| * `none` - Disable cache for the item on both the client and server | ||
| * `segment` - Required segment name, used to isolate cached items within the cache partition. (required) | ||
| * `expiresIn` - relative expiration expressed in the number of milliseconds since the item was saved in the cache. Cannot be used together with `expiresAt`. | ||
| * `expiresAt` - time of day expressed in 24h notation using the 'MM:HH' format, at which point all cache records for the route expire. Cannot be used together with `expiresIn`. | ||
| * `staleIn` - number of milliseconds to mark an item stored in cache as stale and reload it. Must be less than _'expiresIn'_. | ||
| * `staleTimeout` - number of milliseconds to wait before checking if an item is stale | ||
| * `privacy` - optional cache control override for setting _'public'_ or _'private'_ mode. Defaults to _'default'_ (HTTP protocol cache-control defaults). | ||
| * `cache` - a cache client that has been started | ||
| #### Policy Interface | ||
| After a _'Policy'_ is constructed the following methods are available. | ||
| * `isMode` - determines if the policy supports the given mode. (`function (mode)`) | ||
| * `isEnabled` - determines if the policy has a mode enabled. (`function ()`) | ||
| * `get` - retrieve an item from the cache engine if its stored. (`function (key, callback)`) | ||
| * `set` - store an item in the cache at the given key for a specified length of time. (`function (key, value, ttl, callback)`) | ||
| * `drop` - remove the item from cache found at the given key. (`function (key, callback)`) | ||
| * `ttl` - get the number of milliseconds that an item has left before it is expired from a given time. (`function (created)`) | ||
| * `getOrGenerate` - get and item from cache if it exists, or generate it and store it in cache. (`function (key, logFunc, generateFunc, callback)`) | ||
| As a result of the _'Policy'_ constructor taking the segment, the key used should just be the item ID instead of the object used in the cache _'Client'_ previously used. | ||
| ### Examples | ||
| For examples of creating a server that uses one of the above engines look in the _'examples'_ folder. |
@@ -5,5 +5,3 @@ // Load modules | ||
| var Net = require('net'); | ||
| var NodeUtil = require('util'); | ||
| var Events = require('events'); | ||
| var Hapi = require('hapi'); | ||
| var Server = require('./server'); | ||
| var Catbox = require(libPath); | ||
@@ -22,3 +20,2 @@ var Defaults = require(libPath + 'defaults'); | ||
| module.exports = Hapi; | ||
| module.exports.Catbox = Catbox; | ||
@@ -32,10 +29,5 @@ module.exports.Catbox.Defaults = Defaults; | ||
| module.exports.Server = function (host, port, settings) { | ||
| module.exports.Server = function (settings) { | ||
| var server = new Hapi.server(host, port, settings); | ||
| if (settings.cache) { | ||
| server.cache = new Catbox.Client(settings.cache); | ||
| } | ||
| return server; | ||
| return new Server(settings); | ||
| }; | ||
@@ -89,21 +81,2 @@ | ||
| }); | ||
| }; | ||
| internals.Logger = function () { | ||
| Events.EventEmitter.call(this); | ||
| return this; | ||
| }; | ||
| NodeUtil.inherits(internals.Logger, Events.EventEmitter); | ||
| module.exports._TEST = internals.logger = new internals.Logger(); | ||
| // Override Log's console method | ||
| Hapi.log.console = function (message) { | ||
| internals.logger.emit('log', message); | ||
| }; |
+38
-146
@@ -21,67 +21,29 @@ // Load modules | ||
| var _server = null; | ||
| var _serverUrl = 'http://127.0.0.1:17785'; | ||
| var profileHandler = function (request) { | ||
| var activeItemGenerator = function () { | ||
| request.reply({ | ||
| 'id': 'fa0dbda9b1b', | ||
| 'name': 'John Doe' | ||
| }); | ||
| }; | ||
| var activeItemHandler = function (request) { | ||
| request.reply({ | ||
| return { | ||
| 'id': '55cf687663', | ||
| 'name': 'Active Item' | ||
| }); | ||
| }; | ||
| }; | ||
| var cacheItemHandler = function (request) { | ||
| var badGenerator = function () { | ||
| var cacheable = new Helpers.Response.Text('hello'); | ||
| cacheable._code = 200; | ||
| request.reply(cacheable); | ||
| return new Stream(); | ||
| }; | ||
| var badHandler = function (request) { | ||
| var errorGenerator = function () { | ||
| request.reply(new Stream()); | ||
| return new Error('myerror'); | ||
| }; | ||
| var errorHandler = function (request) { | ||
| var error = new Error('myerror'); | ||
| error.code = 500; | ||
| request.reply(error); | ||
| }; | ||
| var notCacheableHandler = function (request) { | ||
| var response = new Helpers.Response.Direct(request) | ||
| .type('text/plain') | ||
| .bytes(13) | ||
| .ttl(1000) | ||
| .write('!hola ') | ||
| .write('amigos!'); | ||
| request.reply(response); | ||
| }; | ||
| function setupServer(done) { | ||
| _server = new Helpers.Server('0.0.0.0', 17785, { cache: { engine: 'memory' } }); | ||
| _server = Helpers.Server({ engine: 'memory' }); | ||
| _server.addRoutes([ | ||
| { method: 'GET', path: '/profile', config: { handler: profileHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/item', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/item2', config: { handler: activeItemHandler, cache: { mode: 'none' } } }, | ||
| { method: 'GET', path: '/item3', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/bad', config: { handler: badHandler, cache: { expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/cache', config: { handler: cacheItemHandler, cache: { expiresIn: 120000, strict: true } } }, | ||
| { method: 'GET', path: '/error', config: { handler: errorHandler, cache: { expiresIn: 120000, strict: true } } }, | ||
| { method: 'GET', path: '/notcacheablenostrict', config: { handler: notCacheableHandler, cache: { expiresIn: 120000, strict: false } } } | ||
| ]); | ||
| _server.addRoute('/item', activeItemGenerator, { mode: 'server', expiresIn: 120000, segment: '/item' }); | ||
| _server.addRoute('/error', errorGenerator, { mode: 'server', expiresIn: 120000, strict: true, segment: '/error' }); | ||
| _server.addRoute('/empty', badGenerator, { mode: 'server', expiresIn: 120000, segment: '/empty' }); | ||
| _server.addRoute('/expired', activeItemGenerator, { mode: 'server', expiresIn: 10, segment: '/expired' }); | ||
@@ -91,77 +53,10 @@ done(); | ||
| var makeRequest = function (path, callback) { | ||
| var next = function (res) { | ||
| return callback(res); | ||
| }; | ||
| _server.inject({ | ||
| method: 'get', | ||
| url: _serverUrl + path | ||
| }, next); | ||
| }; | ||
| var parseHeaders = function (res) { | ||
| var headersObj = {}; | ||
| var headers = res._header.split('\r\n'); | ||
| for (var i = 0, il = headers.length; i < il; i++) { | ||
| var header = headers[i].split(':'); | ||
| var headerValue = header[1] ? header[1].trim() : ''; | ||
| headersObj[header[0]] = headerValue; | ||
| } | ||
| return headersObj; | ||
| }; | ||
| before(setupServer); | ||
| it('returns max-age value when route uses default cache rules', function (done) { | ||
| makeRequest('/profile', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('returns max-age value when route uses client cache mode', function (done) { | ||
| makeRequest('/profile', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('doesn\'t return max-age value when route is not cached', function (done) { | ||
| makeRequest('/item2', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.not.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('throws error when returning a stream in a cached endpoint handler', function (done) { | ||
| function test() { | ||
| makeRequest('/bad', function (rawRes) { }); | ||
| } | ||
| expect(test).to.throw(Error); | ||
| done(); | ||
| }); | ||
| it('doesn\'t cache error responses', function (done) { | ||
| makeRequest('/error', function () { | ||
| _server.getResponse('/error', function () { | ||
| _server.cache.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
| _server.client.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
@@ -174,17 +69,9 @@ expect(cached).to.not.exist; | ||
| it('doesn\'t send cache headers for responses with status codes other than 200', function (done) { | ||
| makeRequest('/nocache', function (res) { | ||
| it('caches non-error responses', function (done) { | ||
| expect(res.headers['Cache-Control']).to.equal('no-cache'); | ||
| done(); | ||
| }); | ||
| }); | ||
| _server.getResponse('/item', function () { | ||
| it('caches responses with status codes of 200', function (done) { | ||
| _server.client.get({ segment: '/item', id: '/item' }, function (err, cached) { | ||
| makeRequest('/cache', function () { | ||
| _server.cache.get({ segment: '/cache', id: '/cache' }, function (err, cached) { | ||
| expect(cached).to.exist; | ||
@@ -196,7 +83,20 @@ done(); | ||
| it('doesn\'t throw an error when requesting a non-strict route that is not cacheable', function (done) { | ||
| it('handles situation where item from cache is expired', function (done) { | ||
| makeRequest('/notcacheablenostrict', function (res) { | ||
| var get = _server.client.connection.get; | ||
| _server.client.connection.get = function (options, callback) { | ||
| expect(res.statusCode).to.equal(200); | ||
| var now = Date.now(); | ||
| callback(null, { | ||
| item: 'myValue', | ||
| stored: now - 20, | ||
| ttl: 1 | ||
| }); | ||
| _server.client.connection.get = get; | ||
| }; | ||
| _server.getResponse('/expired', function (result) { | ||
| expect(result.id).to.equal(activeItemGenerator().id); | ||
| done(); | ||
@@ -206,18 +106,10 @@ }); | ||
| it('throws an error when requesting a strict cached route that is not cacheable', function (done) { | ||
| it('doesn\'t throw an error caching empty streams', function (done) { | ||
| var server = new Helpers.Server('0.0.0.0', 18885, { cache: { engine: 'memory' } }); | ||
| server.addRoute({ method: 'GET', path: '/notcacheable', config: { handler: notCacheableHandler, cache: { expiresIn: 120000, strict: true } } }); | ||
| _server.getResponse('/empty', function (result) { | ||
| var fn = function () { | ||
| server.inject({ | ||
| method: 'get', | ||
| url: 'http://127.0.0.1:18885/notcacheable' | ||
| }); | ||
| }; | ||
| expect(fn).to.throw(Error, 'Attempted to cache non-cacheable item'); | ||
| done(); | ||
| expect(result).to.not.be.instanceOf(Error); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); |
+26
-159
@@ -27,152 +27,47 @@ // Load modules | ||
| var _server = null; | ||
| var _serverUrl = 'http://127.0.0.1:17786'; | ||
| var profileHandler = function (request) { | ||
| var activeItemGenerator = function () { | ||
| request.reply({ | ||
| 'id': 'fa0dbda9b1b', | ||
| 'name': 'John Doe' | ||
| }); | ||
| }; | ||
| var activeItemHandler = function (request) { | ||
| request.reply({ | ||
| return { | ||
| 'id': '55cf687663', | ||
| 'name': 'Active Item' | ||
| }); | ||
| }; | ||
| }; | ||
| var cacheItemHandler = function (request) { | ||
| var badGenerator = function () { | ||
| var cacheable = new Helpers.Response.Text('hello'); | ||
| cacheable._code = 200; | ||
| request.reply(cacheable); | ||
| return new Stream(); | ||
| }; | ||
| var badHandler = function (request) { | ||
| var errorGenerator = function () { | ||
| request.reply(new Stream()); | ||
| return new Error('myerror'); | ||
| }; | ||
| var errorHandler = function (request) { | ||
| var error = new Error('myerror'); | ||
| error.code = 500; | ||
| request.reply(error); | ||
| }; | ||
| var notCacheableHandler = function (request) { | ||
| var response = new Helpers.Response.Direct(request) | ||
| .type('text/plain') | ||
| .bytes(13) | ||
| .ttl(1000) | ||
| .write('!hola ') | ||
| .write('amigos!'); | ||
| request.reply(response); | ||
| }; | ||
| function setupServer(done) { | ||
| _server = new Helpers.Server('0.0.0.0', 17786, { cache: { engine: 'mongodb', partition: 'catbox-test' } }); | ||
| _server = Helpers.Server({ engine: 'mongodb', partition: 'catbox-test' }); | ||
| _server.addRoutes([ | ||
| { method: 'GET', path: '/profile', config: { handler: profileHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/item', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/item2', config: { handler: activeItemHandler, cache: { mode: 'none' } } }, | ||
| { method: 'GET', path: '/item3', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/bad', config: { handler: badHandler, cache: { expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/cache', config: { handler: cacheItemHandler, cache: { expiresIn: 120000, strict: true } } }, | ||
| { method: 'GET', path: '/error', config: { handler: errorHandler, cache: { expiresIn: 120000, strict: true } } }, | ||
| { method: 'GET', path: '/notcacheablenostrict', config: { handler: notCacheableHandler, cache: { expiresIn: 120000, strict: false } } } | ||
| ]); | ||
| _server.addRoute('/item', activeItemGenerator, { mode: 'server', expiresIn: 120000, segment: '/item' }); | ||
| _server.addRoute('/error', errorGenerator, { mode: 'server', expiresIn: 120000, strict: true, segment: '/error' }); | ||
| _server.addRoute('/empty', badGenerator, { mode: 'server', expiresIn: 120000, segment: '/empty' }); | ||
| done(); | ||
| _server.client.start(done); | ||
| } | ||
| var makeRequest = function (path, callback) { | ||
| var next = function (res) { | ||
| return callback(res); | ||
| }; | ||
| _server.inject({ | ||
| method: 'get', | ||
| url: _serverUrl + path | ||
| }, next); | ||
| }; | ||
| var parseHeaders = function (res) { | ||
| var headersObj = {}; | ||
| var headers = res._header.split('\r\n'); | ||
| for (var i = 0, il = headers.length; i < il; i++) { | ||
| var header = headers[i].split(':'); | ||
| var headerValue = header[1] ? header[1].trim() : ''; | ||
| headersObj[header[0]] = headerValue; | ||
| } | ||
| return headersObj; | ||
| }; | ||
| before(setupServer); | ||
| it('returns max-age value when route uses default cache rules', function (done) { | ||
| it('doesn\'t throw an error when calling start when connection already started', function (done) { | ||
| makeRequest('/profile', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| _server.client.start(done); | ||
| }); | ||
| it('returns max-age value when route uses client cache mode', function (done) { | ||
| makeRequest('/profile', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('doesn\'t return max-age value when route is not cached', function (done) { | ||
| makeRequest('/item2', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.not.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('throws error when returning a stream in a cached endpoint handler', function (done) { | ||
| function test() { | ||
| makeRequest('/bad', function (rawRes) { }); | ||
| } | ||
| expect(test).to.throw(Error); | ||
| done(); | ||
| }); | ||
| it('doesn\'t cache error responses', function (done) { | ||
| makeRequest('/error', function () { | ||
| _server.getResponse('/error', function () { | ||
| _server.cache.start(function() { | ||
| _server.client.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
| _server.cache.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
| expect(cached).to.not.exist; | ||
| done(); | ||
| }); | ||
| expect(cached).to.not.exist; | ||
| done(); | ||
| }); | ||
@@ -182,22 +77,11 @@ }); | ||
| it('doesn\'t send cache headers for responses with status codes other than 200', function (done) { | ||
| makeRequest('/nocache', function (res) { | ||
| it('caches non-error responses', function (done) { | ||
| expect(res.headers['Cache-Control']).to.equal('no-cache'); | ||
| done(); | ||
| }); | ||
| }); | ||
| _server.getResponse('/item', function () { | ||
| it('caches responses with status codes of 200', function (done) { | ||
| _server.client.get({ segment: '/item', id: '/item' }, function (err, cached) { | ||
| makeRequest('/cache', function () { | ||
| _server.cache.start(function() { | ||
| _server.cache.get({ segment: '/cache', id: '/cache' }, function (err, cached) { | ||
| expect(cached).to.exist; | ||
| done(); | ||
| }); | ||
| expect(cached).to.exist; | ||
| done(); | ||
| }); | ||
@@ -207,28 +91,11 @@ }); | ||
| it('doesn\'t throw an error when requesting a non-strict route that is not cacheable', function (done) { | ||
| it('doesn\'t throw an error caching empty streams', function (done) { | ||
| makeRequest('/notcacheablenostrict', function (res) { | ||
| _server.getResponse('/empty', function (result) { | ||
| expect(res.statusCode).to.equal(200); | ||
| expect(result).to.not.be.instanceOf(Error); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('throws an error when requesting a strict cached route that is not cacheable', function (done) { | ||
| var server = new Helpers.Server('0.0.0.0', 18885, { cache: { engine: 'memory' } }); | ||
| server.addRoute({ method: 'GET', path: '/notcacheable', config: { handler: notCacheableHandler, cache: { expiresIn: 120000, strict: true } } }); | ||
| var fn = function () { | ||
| server.inject({ | ||
| method: 'get', | ||
| url: 'http://127.0.0.1:18885/notcacheable' | ||
| }); | ||
| }; | ||
| expect(fn).to.throw(Error, 'Attempted to cache non-cacheable item'); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); |
+24
-151
@@ -27,141 +27,42 @@ // Load modules | ||
| var _server = null; | ||
| var _serverUrl = 'http://127.0.0.1:17787'; | ||
| var profileHandler = function (request) { | ||
| var activeItemGenerator = function () { | ||
| request.reply({ | ||
| 'id': 'fa0dbda9b1b', | ||
| 'name': 'John Doe' | ||
| }); | ||
| }; | ||
| var activeItemHandler = function (request) { | ||
| request.reply({ | ||
| return { | ||
| 'id': '55cf687663', | ||
| 'name': 'Active Item' | ||
| }); | ||
| }; | ||
| }; | ||
| var cacheItemHandler = function (request) { | ||
| var badGenerator = function () { | ||
| var cacheable = new Helpers.Response.Text('hello'); | ||
| cacheable._code = 200; | ||
| request.reply(cacheable); | ||
| return new Stream(); | ||
| }; | ||
| var badHandler = function (request) { | ||
| var errorGenerator = function () { | ||
| request.reply(new Stream()); | ||
| return new Error('myerror'); | ||
| }; | ||
| var errorHandler = function (request) { | ||
| var error = new Error('myerror'); | ||
| error.code = 500; | ||
| request.reply(error); | ||
| }; | ||
| var notCacheableHandler = function (request) { | ||
| var response = new Helpers.Response.Direct(request) | ||
| .type('text/plain') | ||
| .bytes(13) | ||
| .ttl(1000) | ||
| .write('!hola ') | ||
| .write('amigos!'); | ||
| request.reply(response); | ||
| }; | ||
| function setupServer(done) { | ||
| _server = new Helpers.Server('0.0.0.0', 17787, { cache: { engine: 'redis', partition: 'catbox-test' } }); | ||
| _server = Helpers.Server({ engine: 'redis', partition: 'catbox-test' }); | ||
| _server.addRoutes([ | ||
| { method: 'GET', path: '/profile', config: { handler: profileHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/item', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/item2', config: { handler: activeItemHandler, cache: { mode: 'none' } } }, | ||
| { method: 'GET', path: '/item3', config: { handler: activeItemHandler, cache: { mode: 'client', expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/bad', config: { handler: badHandler, cache: { expiresIn: 120000 } } }, | ||
| { method: 'GET', path: '/cache', config: { handler: cacheItemHandler, cache: { expiresIn: 120000, strict: true } } }, | ||
| { method: 'GET', path: '/error', config: { handler: errorHandler, cache: { expiresIn: 120000, strict: true } } }, | ||
| { method: 'GET', path: '/notcacheablenostrict', config: { handler: notCacheableHandler, cache: { expiresIn: 120000, strict: false } } } | ||
| ]); | ||
| _server.addRoute('/item', activeItemGenerator, { mode: 'server', expiresIn: 120000, segment: '/item' }); | ||
| _server.addRoute('/error', errorGenerator, { mode: 'server', expiresIn: 120000, strict: true, segment: '/error' }); | ||
| _server.addRoute('/empty', badGenerator, { mode: 'server', expiresIn: 120000, segment: '/empty' }); | ||
| done(); | ||
| _server.client.start(done); | ||
| } | ||
| var makeRequest = function (path, callback) { | ||
| var next = function (res) { | ||
| return callback(res); | ||
| }; | ||
| _server.inject({ | ||
| method: 'get', | ||
| url: _serverUrl + path | ||
| }, next); | ||
| }; | ||
| var parseHeaders = function (res) { | ||
| var headersObj = {}; | ||
| var headers = res._header.split('\r\n'); | ||
| for (var i = 0, il = headers.length; i < il; i++) { | ||
| var header = headers[i].split(':'); | ||
| var headerValue = header[1] ? header[1].trim() : ''; | ||
| headersObj[header[0]] = headerValue; | ||
| } | ||
| return headersObj; | ||
| }; | ||
| before(setupServer); | ||
| it('returns max-age value when route uses default cache rules', function (done) { | ||
| makeRequest('/profile', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('returns max-age value when route uses client cache mode', function (done) { | ||
| makeRequest('/profile', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('doesn\'t return max-age value when route is not cached', function (done) { | ||
| makeRequest('/item2', function (rawRes) { | ||
| var headers = parseHeaders(rawRes.raw.res); | ||
| expect(headers['Cache-Control']).to.not.equal('max-age=120, must-revalidate'); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('doesn\'t cache error responses', function (done) { | ||
| makeRequest('/error', function () { | ||
| _server.getResponse('/error', function () { | ||
| _server.cache.start(function() { | ||
| _server.client.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
| _server.cache.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
| expect(cached).to.not.exist; | ||
| done(); | ||
| }); | ||
| expect(cached).to.not.exist; | ||
| done(); | ||
| }); | ||
@@ -171,22 +72,11 @@ }); | ||
| it('doesn\'t send cache headers for responses with status codes other than 200', function (done) { | ||
| makeRequest('/nocache', function (res) { | ||
| it('caches non-error responses', function (done) { | ||
| expect(res.headers['Cache-Control']).to.equal('no-cache'); | ||
| done(); | ||
| }); | ||
| }); | ||
| _server.getResponse('/item', function () { | ||
| it('caches responses with status codes of 200', function (done) { | ||
| _server.client.get({ segment: '/item', id: '/item' }, function (err, cached) { | ||
| makeRequest('/cache', function () { | ||
| _server.cache.start(function() { | ||
| _server.cache.get({ segment: '/cache', id: '/cache' }, function (err, cached) { | ||
| expect(cached).to.exist; | ||
| done(); | ||
| }); | ||
| expect(cached).to.exist; | ||
| done(); | ||
| }); | ||
@@ -196,28 +86,11 @@ }); | ||
| it('doesn\'t throw an error when requesting a non-strict route that is not cacheable', function (done) { | ||
| it('doesn\'t throw an error caching empty streams', function (done) { | ||
| makeRequest('/notcacheablenostrict', function (res) { | ||
| _server.getResponse('/empty', function (result) { | ||
| expect(res.statusCode).to.equal(200); | ||
| expect(result).to.not.be.instanceOf(Error); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('throws an error when requesting a strict cached route that is not cacheable', function (done) { | ||
| var server = new Helpers.Server('0.0.0.0', 18885, { cache: { engine: 'memory' } }); | ||
| server.addRoute({ method: 'GET', path: '/notcacheable', config: { handler: notCacheableHandler, cache: { expiresIn: 120000, strict: true } } }); | ||
| var fn = function () { | ||
| server.inject({ | ||
| method: 'get', | ||
| url: 'http://127.0.0.1:18885/notcacheable' | ||
| }); | ||
| }; | ||
| expect(fn).to.throw(Error, 'Attempted to cache non-cacheable item'); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); |
@@ -22,7 +22,6 @@ // Load modules | ||
| var options = { | ||
| cache: { | ||
| expiresIn: 100, | ||
| staleIn: 20, | ||
| staleTimeout: 5 | ||
| } | ||
| expiresIn: 100, | ||
| staleIn: 20, | ||
| staleTimeout: 5, | ||
| segment: 'user' | ||
| }; | ||
@@ -39,3 +38,3 @@ | ||
| var server = new Helpers.Server('0.0.0.0', 8097, { cache: 'memory' }); | ||
| var server = Helpers.Server({ engine: 'memory' }); | ||
| server.addHelper('user', method, options); | ||
@@ -68,7 +67,6 @@ | ||
| var options = { | ||
| cache: { | ||
| expiresIn: 100, | ||
| staleIn: 20, | ||
| staleTimeout: 5 | ||
| } | ||
| expiresIn: 100, | ||
| staleIn: 20, | ||
| staleTimeout: 5, | ||
| segment: 'user' | ||
| }; | ||
@@ -91,3 +89,3 @@ | ||
| var server = new Helpers.Server('0.0.0.0', 8097, { cache: 'memory' }); | ||
| var server = Helpers.Server({ engine: 'memory' }); | ||
| server.addHelper('user', method, options); | ||
@@ -125,7 +123,6 @@ | ||
| var options = { | ||
| cache: { | ||
| expiresIn: 100, | ||
| staleIn: 20, | ||
| staleTimeout: 10 | ||
| } | ||
| expiresIn: 100, | ||
| staleIn: 20, | ||
| staleTimeout: 10, | ||
| segment: 'user' | ||
| }; | ||
@@ -139,3 +136,3 @@ | ||
| var server = new Helpers.Server('0.0.0.0', 8097, { cache: 'memory' }); | ||
| var server = Helpers.Server({ engine: 'memory' }); | ||
| server.addHelper('user', method, options); | ||
@@ -168,6 +165,6 @@ | ||
| var server = new Helpers.Server('0.0.0.0', 8097, { cache: 'memory' }); | ||
| server.cache.stop(); | ||
| var server = Helpers.Server({ engine: 'memory' }); | ||
| server.client.stop(); | ||
| var gen = 0; | ||
| server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }, { cache: { expiresIn: 2000 } }); | ||
| server.addHelper('user', function (id, next) { return next({ id: id, gen: ++gen }); }, { expiresIn: 2000, segment: 'user' }); | ||
| var id = Math.random(); | ||
@@ -190,7 +187,6 @@ server.helpers.user(id, function (result1) { | ||
| var options = { | ||
| cache: { | ||
| expiresIn: 30, | ||
| staleIn: 20, | ||
| staleTimeout: 5 | ||
| } | ||
| expiresIn: 30, | ||
| staleIn: 20, | ||
| staleTimeout: 5, | ||
| segment: 'user' | ||
| }; | ||
@@ -210,3 +206,3 @@ | ||
| var server = new Helpers.Server('0.0.0.0', 8097, { cache: 'memory' }); | ||
| var server = Helpers.Server({ engine: 'memory' }); | ||
| server.addHelper('user', method, options); | ||
@@ -213,0 +209,0 @@ |
@@ -159,3 +159,3 @@ // Load modules | ||
| var memory = new Memory.Connection({ maxByteSize: 6 }); | ||
| var memory = new Memory.Connection({ maxByteSize: 30 }); | ||
| expect(memory.cache).to.not.exist; | ||
@@ -202,3 +202,3 @@ | ||
| expect(memory.cache[key1.segment][key1.id].byteSize).to.equal(66); | ||
| expect(memory.cache[key1.segment][key1.id].byteSize).to.equal(102); | ||
| expect(memory.cache[key1.segment][key1.id].item.my).to.exist; | ||
@@ -234,7 +234,7 @@ done(); | ||
| expect(memory.cache[key1.segment][key1.id].byteSize).to.equal(68); | ||
| expect(memory.cache[key1.segment][key1.id].byteSize).to.equal(111); | ||
| expect(memory.cache[key1.segment][key1.id].item.my).to.exist; | ||
| memory.set(key1, itemToStore, 10, function () { | ||
| expect(memory.cache[key1.segment][key1.id].byteSize).to.equal(68); | ||
| expect(memory.cache[key1.segment][key1.id].byteSize).to.equal(111); | ||
| expect(memory.cache[key1.segment][key1.id].item.my).to.exist; | ||
@@ -241,0 +241,0 @@ done(); |
+91
-0
@@ -310,3 +310,94 @@ // Load modules | ||
| }); | ||
| it('stores fresh copy when generation takes longer than ttl to return value', function (done) { | ||
| var key = { id: 'test', segment: 'test' }; | ||
| var cache = new Cache.Memory.Connection(); | ||
| var isGetTtlCalled = false; | ||
| cache.rule = { | ||
| staleTimeout: 1 | ||
| }; | ||
| cache.isMode = function () { | ||
| return true; | ||
| }; | ||
| cache.get = function (key, callback) { | ||
| callback(null, { item: 'testitem', isStale: true, ttl: 3 }); | ||
| }; | ||
| cache.set = function (key) { | ||
| expect(key.id).to.equal('test'); | ||
| }; | ||
| var generateFunc = function (callback) { | ||
| setTimeout(function () { | ||
| callback(null, { getTtl: function () { | ||
| isGetTtlCalled = true; | ||
| return 2; | ||
| }}); | ||
| }, 10); | ||
| }; | ||
| var logFunc = function (tags, data) { | ||
| }; | ||
| cache.start(function () { | ||
| Cache.Stale.process(cache, key, logFunc, ['test'], generateFunc, function (result) { | ||
| expect(result).to.equal('testitem'); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
| it('drops cached item when generation takes longer than ttl and returns an error', function (done) { | ||
| var key = { id: 'test', segment: 'test' }; | ||
| var cache = new Cache.Memory.Connection(); | ||
| var isGetTtlCalled = false; | ||
| cache.rule = { | ||
| staleTimeout: 1 | ||
| }; | ||
| cache.isMode = function () { | ||
| return true; | ||
| }; | ||
| cache.get = function (key, callback) { | ||
| callback(null, { item: 'testitem', isStale: true, ttl: 3 }); | ||
| }; | ||
| cache.drop = function (key, callback) { | ||
| expect(key.id).to.equal('test'); | ||
| callback(); | ||
| }; | ||
| var generateFunc = function (callback) { | ||
| setTimeout(function () { | ||
| callback(new Error()); | ||
| }, 10); | ||
| }; | ||
| var logFunc = function (tags, data) { | ||
| }; | ||
| cache.start(function () { | ||
| Cache.Stale.process(cache, key, logFunc, ['test'], generateFunc, function (result) { | ||
| expect(result).to.equal('testitem'); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
211578
1.29%1
-66.67%29
16%93
1062.5%4
33.33%12
20%4
100%+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated