@ima/core
Version:
IMA.js framework for isomorphic javascript application
189 lines (188 loc) • 6.43 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "CacheImpl", {
enumerable: true,
get: function() {
return CacheImpl;
}
});
const _Cache = require("./Cache");
const _CacheEntry = require("./CacheEntry");
class CacheImpl extends _Cache.Cache {
/**
* Pre-compiled regex for escaping </script tags in serialized output.
* This avoids recompiling the regex on every serialize() call.
*/ static SCRIPT_TAG_REGEX = /<\/script/gi;
_cache;
_factory;
_Helper;
_ttl;
_enabled;
/**
* Initializes the cache.
*
* @param cacheStorage The cache entry storage to use.
* @param factory Which create new instance of cache entry.
* @param Helper The IMA.js helper methods.
* @param config The cache configuration.
*/ constructor(cacheStorage, factory, Helper, { ttl = 30000, enabled = false }){
super();
this._cache = cacheStorage;
this._factory = factory;
/**
* Tha IMA.js helper methods.
*/ this._Helper = Helper;
/**
* Default cache entry time to live in milliseconds.
*/ this._ttl = ttl;
/**
* Flag signalling whether the cache is currently enabled.
*/ this._enabled = enabled;
}
/**
* @inheritDoc
*/ clear() {
this._cache.clear();
}
/**
* @inheritDoc
*/ has(key) {
if (!this._enabled || !this._cache.has(key)) {
return false;
}
const cacheEntry = this._cache.get(key);
if (cacheEntry && !cacheEntry.isExpired()) {
return true;
}
this.delete(key);
return false;
}
/**
* @inheritDoc
*/ get(key) {
if (this.has(key)) {
const cacheEntryItem = this._cache.get(key);
const value = cacheEntryItem.getValue();
return this._clone(value);
}
return null;
}
/**
* @inheritDoc
*/ set(key, value, ttl = 0) {
if (!this._enabled) {
return;
}
const cacheEntry = this._factory.createCacheEntry(this._clone(value), ttl || this._ttl);
this._cache.set(key, cacheEntry);
}
/**
* @inheritDoc
*/ delete(key) {
this._cache.delete(key);
}
/**
* @inheritDoc
*/ disable() {
this._enabled = false;
this.clear();
}
/**
* @inheritDoc
*/ enable() {
this._enabled = true;
}
/**
* @inheritDoc
*/ serialize() {
// Use Object.create(null) to avoid prototype chain overhead
const dataToSerialize = Object.create(null);
for (const key of this._cache.keys()){
const currentValue = this._cache.get(key);
if (currentValue instanceof _CacheEntry.CacheEntry) {
const serializeEntry = currentValue.serialize();
if (serializeEntry.ttl === Infinity) {
serializeEntry.ttl = 'Infinity';
}
if ($Debug) {
if (!this._canSerializeValue(serializeEntry.value)) {
throw new Error(`ima.core.cache.CacheImpl:serialize An ` + `attempt to serialize ` + `${serializeEntry.toString()}, stored ` + `using the key ${key}, was made, but the value ` + `cannot be serialized. Remove this entry from ` + `the cache or change its type so that can be ` + `serialized using JSON.stringify().`);
}
}
dataToSerialize[key] = serializeEntry;
}
}
const serialized = JSON.stringify(dataToSerialize);
// Use pre-compiled regex and only replace if needed
return serialized.includes('</script') ? serialized.replace(CacheImpl.SCRIPT_TAG_REGEX, '<\\/script') : serialized;
}
/**
* @inheritDoc
*/ deserialize(serializedData) {
for(const key in serializedData){
const cacheEntryItem = serializedData[key];
const ttl = cacheEntryItem.ttl === 'Infinity' ? Infinity : cacheEntryItem.ttl;
this.set(key, cacheEntryItem.value, ttl);
}
}
/**
* Tests whether the provided value can be serialized into JSON.
*
* @param value The value to test whether or not it can be serialized.
* @return `true` if the provided value can be serialized into JSON,
* `false` otherwise.
*/ _canSerializeValue(value) {
// Early exit for primitives
const valueType = typeof value;
if (value === null || value === undefined || valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
return true;
}
// Check for non-serializable types
if (value instanceof Date || value instanceof RegExp || value instanceof Promise || valueType === 'function') {
console.warn('The provided value is not serializable: ', value);
return false;
}
// Handle arrays
if (value.constructor === Array) {
for (const element of value){
if (!this._canSerializeValue(element)) {
console.warn('The provided array is not serializable: ', value);
return false;
}
}
return true;
}
// Handle objects
if (valueType === 'object') {
for (const propertyName of Object.keys(value)){
if (!this._canSerializeValue(value[propertyName])) {
console.warn('The provided object is not serializable due to the ' + 'following property: ', propertyName, value);
return false;
}
}
}
return true;
}
/**
* Attempts to clone the provided value, if possible. Values that cannot be
* cloned (e.g. promises) will be simply returned.
*
* @param value The value to clone.
* @return The created clone, or the provided value if the value cannot be
* cloned.
*/ _clone(value) {
// Early exit for null and primitives
if (value == null) {
return value;
}
const valueType = typeof value;
// Only clone objects (arrays, plain objects, etc.)
if (valueType === 'object' && !(value instanceof Promise)) {
return this._Helper.clone(value);
}
return value;
}
}
//# sourceMappingURL=CacheImpl.js.map