Comparing version 7.1.5 to 8.0.0
@@ -33,11 +33,11 @@ 'use strict'; | ||
internals.Client.prototype.stop = function () { | ||
internals.Client.prototype.start = function () { | ||
this.connection.stop(); | ||
return internals.promised(this.connection, 'start'); | ||
}; | ||
internals.Client.prototype.start = function (callback) { | ||
internals.Client.prototype.stop = function () { | ||
this.connection.start(callback); | ||
return this.connection.stop(); | ||
}; | ||
@@ -58,86 +58,70 @@ | ||
internals.Client.prototype.get = function (key, callback) { | ||
internals.Client.prototype.get = async function (key, callback) { | ||
if (!this.connection.isReady()) { | ||
// Disconnected | ||
return callback(Boom.internal('Disconnected')); | ||
throw Boom.internal('Disconnected'); // Disconnected | ||
} | ||
if (!key) { | ||
// Not found on null | ||
return callback(null, null); | ||
return null; // Not found on null | ||
} | ||
if (!internals.validateKey(key)) { | ||
return callback(Boom.internal('Invalid key')); | ||
throw Boom.internal('Invalid key'); | ||
} | ||
this.connection.get(key, (err, result) => { | ||
const result = await internals.promised(this.connection, 'get', key); | ||
if (!result || | ||
result.item === undefined || | ||
result.item === null) { | ||
if (err) { | ||
// Connection error | ||
return callback(err); | ||
} | ||
return null; // Not found | ||
} | ||
if (!result || | ||
result.item === undefined || | ||
result.item === null) { | ||
const now = Date.now(); | ||
const expires = result.stored + result.ttl; | ||
const ttl = expires - now; | ||
if (ttl <= 0) { | ||
return null; // Expired | ||
} | ||
// Not found | ||
return callback(null, null); | ||
} | ||
const cached = { | ||
item: result.item, | ||
stored: result.stored, | ||
ttl | ||
}; | ||
const now = Date.now(); | ||
const expires = result.stored + result.ttl; | ||
const ttl = expires - now; | ||
if (ttl <= 0) { | ||
// Expired | ||
return callback(null, null); | ||
} | ||
// Valid | ||
const cached = { | ||
item: result.item, | ||
stored: result.stored, | ||
ttl | ||
}; | ||
return callback(null, cached); | ||
}); | ||
return cached; // Valid | ||
}; | ||
internals.Client.prototype.set = function (key, value, ttl, callback) { | ||
internals.Client.prototype.set = async function (key, value, ttl) { | ||
if (!this.connection.isReady()) { | ||
// Disconnected | ||
return callback(Boom.internal('Disconnected')); | ||
throw Boom.internal('Disconnected'); // Disconnected | ||
} | ||
if (!internals.validateKey(key)) { | ||
return callback(Boom.internal('Invalid key')); | ||
throw Boom.internal('Invalid key'); | ||
} | ||
if (ttl <= 0) { | ||
// Not cachable (or bad rules) | ||
return callback(); | ||
return; // Not cachable (or bad rules) | ||
} | ||
this.connection.set(key, value, ttl, callback); | ||
return await internals.promised(this.connection, 'set', key, value, ttl); | ||
}; | ||
internals.Client.prototype.drop = function (key, callback) { | ||
internals.Client.prototype.drop = async function (key) { | ||
if (!this.connection.isReady()) { | ||
// Disconnected | ||
return callback(Boom.internal('Disconnected')); | ||
throw Boom.internal('Disconnected'); // Disconnected | ||
} | ||
if (!internals.validateKey(key)) { | ||
return callback(Boom.internal('Invalid key')); | ||
throw Boom.internal('Invalid key'); | ||
} | ||
this.connection.drop(key, callback); // Always drop, regardless of caching rules | ||
return await internals.promised(this.connection, 'drop', key); // Always drop, regardless of caching rules | ||
}; | ||
@@ -150,1 +134,20 @@ | ||
}; | ||
internals.promised = function (obj, method, ...args) { | ||
const deferred = {}; | ||
const callbackPromise = new Promise((resolve, reject) => { | ||
deferred.resolve = resolve; | ||
deferred.reject = reject; | ||
}); | ||
const result = obj[method](...args, (err, value) => { | ||
return err ? deferred.reject(err) : deferred.resolve(value); | ||
}); | ||
const isPromiseResult = !!(result && result.then); | ||
return isPromiseResult ? result : callbackPromise; | ||
}; |
@@ -9,3 +9,5 @@ 'use strict'; | ||
const Pending = require('./pending'); | ||
// Declare internals | ||
@@ -18,45 +20,2 @@ | ||
internals.toBoundCallback = function (callback) { | ||
return process.domain ? process.domain.bind(callback) : callback; | ||
}; | ||
internals.PendingResponse = function (id, addCallback, onDidSend) { | ||
this.id = id; | ||
this.callbacks = [internals.toBoundCallback(addCallback)]; | ||
this.onDidSend = onDidSend; | ||
this.timeoutTimer = null; | ||
}; | ||
internals.PendingResponse.prototype.add = function (callback) { | ||
this.callbacks.push(internals.toBoundCallback(callback)); // Explicitly bind callback to its process.domain (_finalize might get called from a different active process.domain) | ||
}; | ||
internals.PendingResponse.prototype.send = function (err, value, cached, report) { | ||
const length = this.callbacks.length; | ||
for (let i = 0; i < length; ++i) { | ||
Hoek.nextTick(this.callbacks[i])(err, value, cached, report); | ||
} | ||
clearTimeout(this.timeoutTimer); | ||
this.callbacks = []; | ||
return this.onDidSend(length, report); | ||
}; | ||
internals.PendingResponse.prototype.setTimeout = function (fn, timeoutMs) { | ||
clearTimeout(this.timeoutTimer); | ||
this.timeoutTimer = setTimeout(fn, timeoutMs); | ||
}; | ||
exports = module.exports = internals.Policy = function (options, cache, segment) { | ||
@@ -67,3 +26,3 @@ | ||
this._cache = cache; | ||
this._pendings = Object.create(null); // id -> PendingResponse | ||
this._pendings = Object.create(null); // id -> Pending | ||
this._pendingGenerateCall = Object.create(null); // id -> boolean | ||
@@ -96,3 +55,3 @@ this.rules(options); | ||
internals.Policy.prototype.get = function (key, callback) { // key: string or { id: 'id' } | ||
internals.Policy.prototype.get = async function (key) { // key: string or { id: 'id' } | ||
@@ -105,65 +64,69 @@ ++this.stats.gets; | ||
if (this._pendings[id]) { | ||
this._pendings[id].add(callback); | ||
return; | ||
return await this._pendings[id].join(); | ||
} | ||
const pending = this._pendings[id] = new internals.PendingResponse(id, callback, (count, report) => { | ||
const pending = new Pending(id); | ||
this._pendings[id] = pending; | ||
delete this._pendings[id]; | ||
this._get(pending, key); // Background processing | ||
if (count > 0 && report.isStale !== undefined) { | ||
this.stats.hits = this.stats.hits + count; | ||
} | ||
}); | ||
return await pending.promise; | ||
}; | ||
// Lookup in cache | ||
const timer = new Hoek.Timer(); | ||
this._get(id, (err, cached) => { | ||
internals.Policy.prototype._get = async function (pending, key) { | ||
if (err) { | ||
++this.stats.errors; | ||
} | ||
// Prepare report | ||
// Prepare report | ||
const report = {}; | ||
const report = { | ||
msec: timer.elapsed(), | ||
error: err | ||
}; | ||
// Lookup in cache | ||
if (cached) { | ||
report.stored = cached.stored; | ||
report.ttl = cached.ttl; | ||
const staleIn = typeof this.rule.staleIn === 'function' ? this.rule.staleIn(cached.stored, cached.ttl) : this.rule.staleIn; | ||
cached.isStale = (staleIn ? (Date.now() - cached.stored) >= staleIn : false); | ||
report.isStale = cached.isStale; | ||
const timer = new Hoek.Bench(); | ||
let cached; | ||
try { | ||
cached = (this._cache ? await this._cache.get({ segment: this._segment, id: pending.id }) : null); | ||
} | ||
catch (err) { | ||
report.error = err; | ||
++this.stats.errors; | ||
} | ||
if (cached.isStale) { | ||
++this.stats.stales; | ||
} | ||
report.msec = timer.elapsed(); | ||
if (cached) { | ||
report.stored = cached.stored; | ||
report.ttl = cached.ttl; | ||
const staleIn = typeof this.rule.staleIn === 'function' ? this.rule.staleIn(cached.stored, cached.ttl) : this.rule.staleIn; | ||
cached.isStale = (staleIn ? (Date.now() - cached.stored) >= staleIn : false); | ||
report.isStale = cached.isStale; | ||
if (cached.isStale) { | ||
++this.stats.stales; | ||
} | ||
} | ||
// No generate method | ||
// No generate method | ||
if (!this.rule.generateFunc || | ||
(err && !this.rule.generateOnReadError)) { | ||
if (!this.rule.generateFunc || | ||
(report.error && !this.rule.generateOnReadError)) { | ||
return pending.send(err, cached ? cached.item : null, cached, report); | ||
} | ||
this._send(pending, report.error, cached ? cached.item : null, cached, report); | ||
return; | ||
} | ||
// Check if found and fresh | ||
// Check if found and fresh | ||
if (cached && | ||
!cached.isStale) { | ||
if (cached && | ||
!cached.isStale) { | ||
return pending.send(null, cached.item, cached, report); | ||
} | ||
this._send(pending, null, cached.item, cached, report); | ||
return; | ||
} | ||
return this._generate(pending, key, cached, report); | ||
}); | ||
await this._generate(pending, key, cached, report); | ||
}; | ||
internals.Policy.prototype._generate = function (pending, key, cached, report) { | ||
internals.Policy.prototype._generate = async function (pending, key, cached, report) { | ||
@@ -182,3 +145,3 @@ if (cached) { // Must be stale | ||
return pending.send(null, cached.item, cached, report); | ||
return this._send(pending, null, cached.item, cached, report); | ||
}, this.rule.staleTimeout); | ||
@@ -192,3 +155,3 @@ } | ||
return pending.send(Boom.serverUnavailable(), null, null, report); | ||
return this._send(pending, Boom.serverUnavailable(), null, null, report); | ||
}, this.rule.generateTimeout); | ||
@@ -210,9 +173,3 @@ } | ||
try { | ||
this._callGenerateFunc(pending, key, cached, report); | ||
} | ||
catch (err) { | ||
delete this._pendingGenerateCall[pending.id]; | ||
return pending.send(err, null, null, report); | ||
} | ||
await this._callGenerateFunc(pending, key, cached, report); | ||
} | ||
@@ -225,55 +182,62 @@ else { | ||
internals.Policy.prototype._callGenerateFunc = function (pending, key, cached, report) { | ||
internals.Policy.prototype._callGenerateFunc = async function (pending, key, cached, report) { | ||
this.rule.generateFunc.call(null, key, (generateError, value, ttl) => { | ||
let value; | ||
const flags = {}; | ||
let generateError; | ||
try { | ||
value = await this.rule.generateFunc(key, flags); | ||
} | ||
catch (err) { | ||
generateError = err; | ||
} | ||
pending = this._pendingGenerateCall[pending.id] || pending; | ||
delete this._pendingGenerateCall[pending.id]; | ||
pending = this._pendingGenerateCall[pending.id] || pending; | ||
delete this._pendingGenerateCall[pending.id]; | ||
const finalize = (err) => { | ||
// Error (if dropOnError is not set to false) or not cached | ||
const error = generateError || (this.rule.generateIgnoreWriteError ? null : err); | ||
if (cached && | ||
error && | ||
!this.rule.dropOnError) { | ||
let persistError; | ||
try { | ||
if ((generateError && this.rule.dropOnError) || flags.ttl === 0) { // null or undefined means use policy | ||
await this.drop(pending.id); // Invalidate cache | ||
} | ||
else if (!generateError) { | ||
await this.set(pending.id, value, flags.ttl); // Lazy save (replaces stale cache copy with late-coming fresh copy) | ||
} | ||
} | ||
catch (err) { | ||
persistError = err; | ||
} | ||
return pending.send(error, cached.item, cached, report); | ||
} | ||
const error = generateError || (this.rule.generateIgnoreWriteError ? null : persistError); | ||
if (cached && | ||
error && | ||
!this.rule.dropOnError) { | ||
return pending.send(error, value, null, report); // Ignored if stale value already returned | ||
}; | ||
this._send(pending, error, cached.item, cached, report); | ||
return; | ||
} | ||
// Error (if dropOnError is not set to false) or not cached | ||
this._send(pending, error, value, null, report); // Ignored if stale value already returned | ||
}; | ||
if ((generateError && this.rule.dropOnError) || ttl === 0) { // null or undefined means use policy | ||
return this.drop(pending.id, finalize); // Invalidate cache | ||
} | ||
if (!generateError) { | ||
return this.set(pending.id, value, ttl, finalize); // Lazy save (replaces stale cache copy with late-coming fresh copy) | ||
} | ||
internals.Policy.prototype._send = function (pending, err, value, cached, report) { | ||
return finalize(); | ||
}); | ||
}; | ||
pending.send(err, value, cached, report); | ||
delete this._pendings[pending.id]; | ||
internals.Policy.prototype._get = function (id, callback) { | ||
if (!this._cache) { | ||
return Hoek.nextTick(callback)(null, null); | ||
if (report.isStale !== undefined) { | ||
this.stats.hits = this.stats.hits + pending.count; | ||
} | ||
this._cache.get({ segment: this._segment, id }, callback); | ||
}; | ||
internals.Policy.prototype.set = function (key, value, ttl, callback) { | ||
internals.Policy.prototype.set = async function (key, value, ttl) { | ||
callback = callback || Hoek.ignore; | ||
++this.stats.sets; | ||
if (!this._cache) { | ||
return callback(null); | ||
return; | ||
} | ||
@@ -283,30 +247,26 @@ | ||
const id = (key && typeof key === 'object') ? key.id : key; | ||
this._cache.set({ segment: this._segment, id }, value, ttl, (err) => { | ||
if (err) { | ||
++this.stats.errors; | ||
} | ||
return callback(err); | ||
}); | ||
try { | ||
await this._cache.set({ segment: this._segment, id }, value, ttl); | ||
} | ||
catch (err) { | ||
++this.stats.errors; | ||
throw err; | ||
} | ||
}; | ||
internals.Policy.prototype.drop = function (key, callback) { | ||
internals.Policy.prototype.drop = async function (key) { | ||
callback = callback || Hoek.ignore; | ||
if (!this._cache) { | ||
return callback(null); | ||
return; | ||
} | ||
const id = (key && typeof key === 'object') ? key.id : key; | ||
this._cache.drop({ segment: this._segment, id }, (err) => { | ||
if (err) { | ||
++this.stats.errors; | ||
} | ||
return callback(err); | ||
}); | ||
try { | ||
return await this._cache.drop({ segment: this._segment, id }); | ||
} | ||
catch (err) { | ||
++this.stats.errors; | ||
throw err; | ||
} | ||
}; | ||
@@ -358,4 +318,4 @@ | ||
expiresAt: '13:00', | ||
generateFunc: function (id, next) { next(err, result, ttl); } | ||
generateFunc: (id, flags) => { throw err; } / { return result; } / { flags.ttl = ttl; return result; } | ||
generateTimeout: 500, | ||
@@ -362,0 +322,0 @@ generateOnReadError: true, |
{ | ||
"name": "catbox", | ||
"description": "Multi-strategy object caching service", | ||
"version": "7.1.5", | ||
"version": "8.0.0", | ||
"repository": "git://github.com/hapijs/catbox", | ||
@@ -13,11 +13,11 @@ "main": "lib/index.js", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
"node": ">=8.0.0" | ||
}, | ||
"dependencies": { | ||
"boom": "5.x.x", | ||
"hoek": "4.x.x", | ||
"joi": "10.x.x" | ||
"boom": "6.x.x", | ||
"hoek": "5.x.x", | ||
"joi": "11.x.x" | ||
}, | ||
"devDependencies": { | ||
"code": "4.x.x", | ||
"code": "5.x.x", | ||
"lab": "14.x.x" | ||
@@ -24,0 +24,0 @@ }, |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
31607
8
448
0
+ Addedboom@6.0.0(transitive)
+ Addedhoek@5.0.4(transitive)
+ Addedisemail@3.2.0(transitive)
+ Addedjoi@11.4.0(transitive)
+ Addedpunycode@2.3.1(transitive)
- Removedboom@5.3.3(transitive)
- Removedisemail@2.2.1(transitive)
- Removeditems@2.2.1(transitive)
- Removedjoi@10.6.0(transitive)
Updatedboom@6.x.x
Updatedhoek@5.x.x
Updatedjoi@11.x.x