UNPKG

js-cache

Version:

Caching library supporting timeouts, events and external data sources

542 lines (479 loc) 16.9 kB
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.cache=e():"undefined"!=typeof global?global.cache=e():"undefined"!=typeof self&&(self.cache=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ module.exports = require('./lib/cache'); },{"./lib/cache":2}],2:[function(require,module,exports){ var process=require("__browserify_process");var timeout = require('infinite-timeout'); var events = require('backbone-events-standalone'); /** * Find out if an object has a certain property * * @param {object} object * @param {string} key */ function has(object, key){ return Object.prototype.hasOwnProperty.call(object, key); } /** * Cache class */ function Cache(){ this.initialize.apply(this, arguments); } var _ = Cache.prototype; events.mixin(_); /** * Constructor * * @param {object} external Forward set, get, clear and del commands to an external handler (optional) */ _.initialize = function(external){ this.store = {}; this.external = external; }; /** * Insert or overwrite data * * @param {string} key * @param {mixed} value * @param {number} ttl Time to live in milliseconds (optional) */ _.set = function(key, value, ttl){ if(typeof key === 'undefined') throw new Error('Required argument key is undefined'); // Clear timeout on existing record var oldRecord = has(this.store, key)? this.store[key] : undefined; if(oldRecord && oldRecord.timeout){ timeout.clear(oldRecord.timeout); } // Set value + timeout on new record var record = {value: value}; if(typeof ttl === 'number'){ record.timeout = timeout.set(this.delInternal.bind(this, key), ttl); } this.store[key] = record; // Call external handler if(this.external && typeof this.external.set === 'function'){ this.external.set(key, value, ttl); } // Emit update/set events var action = oldRecord? 'update' : 'set'; this.trigger(action, key, value, ttl); this.trigger(action + ':' + key, value, ttl); }; /** * Get cached data * * @param {string} key * @param {function} callback Return value in callback if records exists locally or on external resource (optional) * @return {mixed} value Only returned if callback is undefined */ _.get = function(key, callback){ if(typeof key === 'undefined') throw new Error('Required argument key is undefined'); if(has(this.store, key)){ if(typeof callback === 'function'){ process.nextTick(callback.bind(null, this.store[key].value)); }else{ return this.store[key].value; } }else if(typeof callback === 'function' && this.external && typeof this.external.get === 'function'){ this.external.get(key, callback); } }; /** * Delete cached data * * @param {string} key * @return {boolean} Returns true if record existed */ _.del = function(key){ if(typeof key === 'undefined') throw new Error('Required argument key is undefined'); if(this.external && typeof this.external.del === 'function'){ this.external.del(key); } return this.delInternal(key); }; _.delInternal = function(key){ if(has(this.store, key)){ // Clear timeout if(this.store[key].timeout){ timeout.clear(this.store[key].timeout); } // Delete record delete this.store[key]; // Emit del events this.trigger('del', key); this.trigger('del:' + key); return true; }else{ return false; } }; /** * Clear cached data * * @return {number} Returns number of cleared records */ _.clear = function(){ var size = this.size(); this.store = {}; if(this.external && typeof this.external.clear === 'function'){ this.external.clear(); } this.trigger('clear', size); return size; }; /** * Retrieve number of records * * @return {number} */ _.size = function(){ var size = 0; for(var key in this.store){ if(has(this.store, key)) size++; } return size; }; /** * Retrieve internal store * * @return {object} */ _.debug = function(){ return this.store; }; /** * Export a combined instance and constructor */ var instance = new Cache(); var constructor = function(){ return new Cache(arguments[0]); }; constructor.Cache = Cache; for(var key in _){ if(has(_, key) && typeof _[key] === 'function'){ constructor[key] = _[key].bind(instance); } } module.exports = constructor; },{"__browserify_process":7,"backbone-events-standalone":4,"infinite-timeout":5}],3:[function(require,module,exports){ /** * Standalone extraction of Backbone.Events, no external dependency required. * Degrades nicely when Backone/underscore are already available in the current * global context. * * Note that docs suggest to use underscore's `_.extend()` method to add Events * support to some given object. A `mixin()` method has been added to the Events * prototype to avoid using underscore for that sole purpose: * * var myEventEmitter = BackboneEvents.mixin({}); * * Or for a function constructor: * * function MyConstructor(){} * MyConstructor.prototype.foo = function(){} * BackboneEvents.mixin(MyConstructor.prototype); * * (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. * (c) 2013 Nicolas Perriault */ /* global exports:true, define, module */ (function() { var root = this, breaker = {}, nativeForEach = Array.prototype.forEach, hasOwnProperty = Object.prototype.hasOwnProperty, slice = Array.prototype.slice, idCounter = 0; // Returns a partial implementation matching the minimal API subset required // by Backbone.Events function miniscore() { return { keys: Object.keys, uniqueId: function(prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; }, has: function(obj, key) { return hasOwnProperty.call(obj, key); }, each: function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { if (this.has(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) return; } } } }, once: function(func) { var ran = false, memo; return function() { if (ran) return memo; ran = true; memo = func.apply(this, arguments); func = null; return memo; }; } }; } var _ = miniscore(), Events; // Backbone.Events // --------------- // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback // functions to an event; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // Events = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. on: function(name, callback, context) { if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; this._events || (this._events = {}); var events = this._events[name] || (this._events[name] = []); events.push({callback: callback, context: context, ctx: context || this}); return this; }, // Bind an event to only be triggered a single time. After the first time // the callback is invoked, it will be removed. once: function(name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; var once = _.once(function() { self.off(name, once); callback.apply(this, arguments); }); once._callback = callback; return this.on(name, once, context); }, // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!name && !callback && !context) { this._events = {}; return this; } names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) { name = names[i]; if (events = this._events[name]) { this._events[name] = retain = []; if (callback || context) { for (j = 0, k = events.length; j < k; j++) { ev = events[j]; if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { retain.push(ev); } } } if (!retain.length) delete this._events[name]; } } return this; }, // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). trigger: function(name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; var events = this._events[name]; var allEvents = this._events.all; if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, arguments); return this; }, // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. stopListening: function(obj, name, callback) { var listeners = this._listeners; if (!listeners) return this; var deleteListener = !name && !callback; if (typeof name === 'object') callback = this; if (obj) (listeners = {})[obj._listenerId] = obj; for (var id in listeners) { listeners[id].off(name, callback, this); if (deleteListener) delete this._listeners[id]; } return this; } }; // Regular expression used to split event strings. var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event // names `"change blur"` and jQuery-style event maps `{change: action}` // in terms of the existing API. var eventsApi = function(obj, action, name, rest) { if (!name) return true; // Handle event maps. if (typeof name === 'object') { for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); } return false; } // Handle space separated event names. if (eventSplitter.test(name)) { var names = name.split(eventSplitter); for (var i = 0, l = names.length; i < l; i++) { obj[action].apply(obj, [names[i]].concat(rest)); } return false; } return true; }; // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); } }; var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; // Inversion-of-control versions of `on` and `once`. Tell *this* object to // listen to an event in another object ... keeping track of what it's // listening to. _.each(listenMethods, function(implementation, method) { Events[method] = function(obj, name, callback) { var listeners = this._listeners || (this._listeners = {}); var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); listeners[id] = obj; if (typeof name === 'object') callback = this; obj[implementation](name, callback, this); return this; }; }); // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; // Mixin utility Events.mixin = function(proto) { var exports = ['on', 'once', 'off', 'trigger', 'stopListening', 'listenTo', 'listenToOnce', 'bind', 'unbind']; _.each(exports, function(name) { proto[name] = this[name]; }, this); return proto; }; // Export Events as BackboneEvents depending on current context if (typeof define === "function") { define(function() { return Events; }); } else if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = Events; } exports.BackboneEvents = Events; } else { root.BackboneEvents = Events; } })(this); },{}],4:[function(require,module,exports){ module.exports = require('./backbone-events-standalone'); },{"./backbone-events-standalone":3}],5:[function(require,module,exports){ module.exports = require('./lib/timeout'); },{"./lib/timeout":6}],6:[function(require,module,exports){ (function(exports){ var MAX_INT = 2147483647; var timeouts = {}; var index = 1; // Set new timeout var set = function(callback, timeout){ var id = index++; if(timeout > MAX_INT){ timeouts[id] = setTimeout(set.bind(undefined, callback, timeout - MAX_INT), MAX_INT); }else{ if(timeout < 0) timeout = 0; timeouts[id] = setTimeout(function(){ delete timeouts[id]; callback(); }, timeout); } return id; }; // Clear existing timeout var clear = function(id){ if(timeouts.hasOwnProperty(id)){ clearTimeout(timeouts[id]); delete timeouts[id]; } }; // Expose public interface exports.set = set; exports.clear = clear; exports._timeouts = timeouts; })(typeof module === 'undefined' && typeof exports === 'undefined'? this.timeout = {} : exports); },{}],7:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; process.nextTick = (function () { var canSetImmediate = typeof window !== 'undefined' && window.setImmediate; var canPost = typeof window !== 'undefined' && window.postMessage && window.addEventListener ; if (canSetImmediate) { return function (f) { return window.setImmediate(f) }; } if (canPost) { var queue = []; window.addEventListener('message', function (ev) { if (ev.source === window && ev.data === 'process-tick') { ev.stopPropagation(); if (queue.length > 0) { var fn = queue.shift(); fn(); } } }, true); return function nextTick(fn) { queue.push(fn); window.postMessage('process-tick', '*'); }; } return function nextTick(fn) { setTimeout(fn, 0); }; })(); process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.binding = function (name) { throw new Error('process.binding is not supported'); } // TODO(shtylman) process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; },{}]},{},[1]) (1) }); ;