Innovenergy_trunk/frontend/node_modules/replicator/index.js

579 lines
16 KiB
JavaScript

// Const
var TRANSFORMED_TYPE_KEY = '@t';
var CIRCULAR_REF_KEY = '@r';
var KEY_REQUIRE_ESCAPING_RE = /^#*@(t|r)$/;
var GLOBAL = (function getGlobal () {
// NOTE: see http://www.ecma-international.org/ecma-262/6.0/index.html#sec-performeval step 10
var savedEval = eval;
return savedEval('this');
})();
var TYPED_ARRAY_CTORS = {
'Int8Array': typeof Int8Array === 'function' ? Int8Array : void 0,
'Uint8Array': typeof Uint8Array === 'function' ? Uint8Array : void 0,
'Uint8ClampedArray': typeof Uint8ClampedArray === 'function' ? Uint8ClampedArray : void 0,
'Int16Array': typeof Int16Array === 'function' ? Int16Array : void 0,
'Uint16Array': typeof Uint16Array === 'function' ? Uint16Array : void 0,
'Int32Array': typeof Int32Array === 'function' ? Int32Array : void 0,
'Uint32Array': typeof Uint32Array === 'function' ? Uint32Array : void 0,
'Float32Array': typeof Float32Array === 'function' ? Float32Array : void 0,
'Float64Array': typeof Float64Array === 'function' ? Float64Array : void 0
};
var ARRAY_BUFFER_SUPPORTED = typeof ArrayBuffer === 'function';
var MAP_SUPPORTED = typeof Map === 'function';
var SET_SUPPORTED = typeof Set === 'function';
var BUFFER_FROM_SUPPORTED = typeof Buffer === 'function';
var TYPED_ARRAY_SUPPORTED = function (typeName) {
return !!TYPED_ARRAY_CTORS[typeName];
};
// Saved proto functions
var arrSlice = Array.prototype.slice;
// Default serializer
var JSONSerializer = {
serialize: function (val) {
return JSON.stringify(val);
},
deserialize: function (val) {
return JSON.parse(val);
}
};
// EncodingTransformer
var EncodingTransformer = function (val, transforms) {
this.references = val;
this.transforms = transforms;
this.circularCandidates = [];
this.circularCandidatesDescrs = [];
this.circularRefCount = 0;
};
EncodingTransformer._createRefMark = function (idx) {
var obj = Object.create(null);
obj[CIRCULAR_REF_KEY] = idx;
return obj;
};
EncodingTransformer.prototype._createCircularCandidate = function (val, parent, key) {
this.circularCandidates.push(val);
this.circularCandidatesDescrs.push({ parent: parent, key: key, refIdx: -1 });
};
EncodingTransformer.prototype._applyTransform = function (val, parent, key, transform) {
var result = Object.create(null);
var serializableVal = transform.toSerializable(val);
if (typeof serializableVal === 'object')
this._createCircularCandidate(val, parent, key);
result[TRANSFORMED_TYPE_KEY] = transform.type;
result.data = this._handleValue(serializableVal, parent, key);
return result;
};
EncodingTransformer.prototype._handleArray = function (arr) {
var result = [];
for (var i = 0; i < arr.length; i++)
result[i] = this._handleValue(arr[i], result, i);
return result;
};
EncodingTransformer.prototype._handlePlainObject = function (obj) {
var replicator = this;
var result = Object.create(null);
var ownPropertyNames = Object.getOwnPropertyNames(obj);
ownPropertyNames.forEach(function (key) {
var resultKey = KEY_REQUIRE_ESCAPING_RE.test(key) ? '#' + key : key;
result[resultKey] = replicator._handleValue(obj[key], result, resultKey);
});
return result;
};
EncodingTransformer.prototype._handleObject = function (obj, parent, key) {
this._createCircularCandidate(obj, parent, key);
return Array.isArray(obj) ? this._handleArray(obj) : this._handlePlainObject(obj);
};
EncodingTransformer.prototype._ensureCircularReference = function (obj) {
var circularCandidateIdx = this.circularCandidates.indexOf(obj);
if (circularCandidateIdx > -1) {
var descr = this.circularCandidatesDescrs[circularCandidateIdx];
if (descr.refIdx === -1)
descr.refIdx = descr.parent ? ++this.circularRefCount : 0;
return EncodingTransformer._createRefMark(descr.refIdx);
}
return null;
};
EncodingTransformer.prototype._handleValue = function (val, parent, key) {
var type = typeof val;
var isObject = type === 'object' && val !== null;
if (isObject) {
var refMark = this._ensureCircularReference(val);
if (refMark)
return refMark;
}
for (var i = 0; i < this.transforms.length; i++) {
var transform = this.transforms[i];
if (transform.shouldTransform(type, val))
return this._applyTransform(val, parent, key, transform);
}
if (isObject)
return this._handleObject(val, parent, key);
return val;
};
EncodingTransformer.prototype.transform = function () {
var references = [this._handleValue(this.references, null, null)];
for (var i = 0; i < this.circularCandidatesDescrs.length; i++) {
var descr = this.circularCandidatesDescrs[i];
if (descr.refIdx > 0) {
references[descr.refIdx] = descr.parent[descr.key];
descr.parent[descr.key] = EncodingTransformer._createRefMark(descr.refIdx);
}
}
return references;
};
// DecodingTransform
var DecodingTransformer = function (references, transformsMap) {
this.references = references;
this.transformMap = transformsMap;
this.activeTransformsStack = [];
this.visitedRefs = Object.create(null);
};
DecodingTransformer.prototype._handlePlainObject = function (obj) {
var replicator = this;
var unescaped = Object.create(null);
var ownPropertyNames = Object.getOwnPropertyNames(obj);
ownPropertyNames.forEach(function (key) {
replicator._handleValue(obj[key], obj, key);
if (KEY_REQUIRE_ESCAPING_RE.test(key)) {
// NOTE: use intermediate object to avoid unescaped and escaped keys interference
// E.g. unescaped "##@t" will be "#@t" which can overwrite escaped "#@t".
unescaped[key.substring(1)] = obj[key];
delete obj[key];
}
});
for (var unsecapedKey in unescaped)
obj[unsecapedKey] = unescaped[unsecapedKey];
};
DecodingTransformer.prototype._handleTransformedObject = function (obj, parent, key) {
var transformType = obj[TRANSFORMED_TYPE_KEY];
var transform = this.transformMap[transformType];
if (!transform)
throw new Error('Can\'t find transform for "' + transformType + '" type.');
this.activeTransformsStack.push(obj);
this._handleValue(obj.data, obj, 'data');
this.activeTransformsStack.pop();
parent[key] = transform.fromSerializable(obj.data);
};
DecodingTransformer.prototype._handleCircularSelfRefDuringTransform = function (refIdx, parent, key) {
// NOTE: we've hit a hard case: object reference itself during transformation.
// We can't dereference it since we don't have resulting object yet. And we'll
// not be able to restore reference lately because we will need to traverse
// transformed object again and reference might be unreachable or new object contain
// new circular references. As a workaround we create getter, so once transformation
// complete, dereferenced property will point to correct transformed object.
var references = this.references;
var val = void 0;
Object.defineProperty(parent, key, {
configurable: true,
enumerable: true,
get: function () {
if (val === void 0)
val = references[refIdx];
return val;
},
set: function (value) {
val = value;
return val;
}
});
};
DecodingTransformer.prototype._handleCircularRef = function (refIdx, parent, key) {
if (this.activeTransformsStack.indexOf(this.references[refIdx]) > -1)
this._handleCircularSelfRefDuringTransform(refIdx, parent, key);
else {
if (!this.visitedRefs[refIdx]) {
this.visitedRefs[refIdx] = true;
this._handleValue(this.references[refIdx], this.references, refIdx);
}
parent[key] = this.references[refIdx];
}
};
DecodingTransformer.prototype._handleValue = function (val, parent, key) {
if (typeof val !== 'object' || val === null)
return;
var refIdx = val[CIRCULAR_REF_KEY];
if (refIdx !== void 0)
this._handleCircularRef(refIdx, parent, key);
else if (val[TRANSFORMED_TYPE_KEY])
this._handleTransformedObject(val, parent, key);
else if (Array.isArray(val)) {
for (var i = 0; i < val.length; i++)
this._handleValue(val[i], val, i);
}
else
this._handlePlainObject(val);
};
DecodingTransformer.prototype.transform = function () {
this.visitedRefs[0] = true;
this._handleValue(this.references[0], this.references, 0);
return this.references[0];
};
// Transforms
var builtInTransforms = [
{
type: '[[NaN]]',
shouldTransform: function (type, val) {
return type === 'number' && isNaN(val);
},
toSerializable: function () {
return '';
},
fromSerializable: function () {
return NaN;
}
},
{
type: '[[undefined]]',
shouldTransform: function (type) {
return type === 'undefined';
},
toSerializable: function () {
return '';
},
fromSerializable: function () {
return void 0;
}
},
{
type: '[[Date]]',
shouldTransform: function (type, val) {
return val instanceof Date;
},
toSerializable: function (date) {
return date.getTime();
},
fromSerializable: function (val) {
var date = new Date();
date.setTime(val);
return date;
}
},
{
type: '[[RegExp]]',
shouldTransform: function (type, val) {
return val instanceof RegExp;
},
toSerializable: function (re) {
var result = {
src: re.source,
flags: ''
};
if (re.global)
result.flags += 'g';
if (re.ignoreCase)
result.flags += 'i';
if (re.multiline)
result.flags += 'm';
return result;
},
fromSerializable: function (val) {
return new RegExp(val.src, val.flags);
}
},
{
type: '[[Error]]',
shouldTransform: function (type, val) {
return val instanceof Error;
},
toSerializable: function (err) {
return {
name: err.name,
message: err.message,
stack: err.stack
};
},
fromSerializable: function (val) {
var Ctor = GLOBAL[val.name] || Error;
var err = new Ctor(val.message);
err.stack = val.stack;
return err;
}
},
{
type: '[[ArrayBuffer]]',
shouldTransform: function (type, val) {
return ARRAY_BUFFER_SUPPORTED && val instanceof ArrayBuffer;
},
toSerializable: function (buffer) {
var view = new Int8Array(buffer);
return arrSlice.call(view);
},
fromSerializable: function (val) {
if (ARRAY_BUFFER_SUPPORTED) {
var buffer = new ArrayBuffer(val.length);
var view = new Int8Array(buffer);
view.set(val);
return buffer;
}
return val;
}
},
{
type: '[[Buffer]]',
shouldTransform: function (type, val) {
return BUFFER_FROM_SUPPORTED && val instanceof Buffer;
},
toSerializable: function (buffer) {
return arrSlice.call(buffer);
},
fromSerializable: function (val) {
if (BUFFER_FROM_SUPPORTED)
return Buffer.from(val);
return val;
}
},
{
type: '[[TypedArray]]',
shouldTransform: function (type, val) {
return Object.keys(TYPED_ARRAY_CTORS).some(function (ctorName) {
return TYPED_ARRAY_SUPPORTED(ctorName) && val instanceof TYPED_ARRAY_CTORS[ctorName];
});
},
toSerializable: function (arr) {
return {
ctorName: arr.constructor.name,
arr: arrSlice.call(arr)
};
},
fromSerializable: function (val) {
return TYPED_ARRAY_SUPPORTED(val.ctorName) ? new TYPED_ARRAY_CTORS[val.ctorName](val.arr) : val.arr;
}
},
{
type: '[[Map]]',
shouldTransform: function (type, val) {
return MAP_SUPPORTED && val instanceof Map;
},
toSerializable: function (map) {
var flattenedKVArr = [];
map.forEach(function (val, key) {
flattenedKVArr.push(key);
flattenedKVArr.push(val);
});
return flattenedKVArr;
},
fromSerializable: function (val) {
if (MAP_SUPPORTED) {
// NOTE: new Map(iterable) is not supported by all browsers
var map = new Map();
for (var i = 0; i < val.length; i += 2)
map.set(val[i], val[i + 1]);
return map;
}
var kvArr = [];
for (var j = 0; j < val.length; j += 2)
kvArr.push([val[i], val[i + 1]]);
return kvArr;
}
},
{
type: '[[Set]]',
shouldTransform: function (type, val) {
return SET_SUPPORTED && val instanceof Set;
},
toSerializable: function (set) {
var arr = [];
set.forEach(function (val) {
arr.push(val);
});
return arr;
},
fromSerializable: function (val) {
if (SET_SUPPORTED) {
// NOTE: new Set(iterable) is not supported by all browsers
var set = new Set();
for (var i = 0; i < val.length; i++)
set.add(val[i]);
return set;
}
return val;
}
}
];
// Replicator
var Replicator = module.exports = function (serializer) {
this.transforms = [];
this.transformsMap = Object.create(null);
this.serializer = serializer || JSONSerializer;
this.addTransforms(builtInTransforms);
};
// Manage transforms
Replicator.prototype.addTransforms = function (transforms) {
transforms = Array.isArray(transforms) ? transforms : [transforms];
for (var i = 0; i < transforms.length; i++) {
var transform = transforms[i];
if (this.transformsMap[transform.type])
throw new Error('Transform with type "' + transform.type + '" was already added.');
this.transforms.push(transform);
this.transformsMap[transform.type] = transform;
}
return this;
};
Replicator.prototype.removeTransforms = function (transforms) {
transforms = Array.isArray(transforms) ? transforms : [transforms];
for (var i = 0; i < transforms.length; i++) {
var transform = transforms[i];
var idx = this.transforms.indexOf(transform);
if (idx > -1)
this.transforms.splice(idx, 1);
delete this.transformsMap[transform.type];
}
return this;
};
Replicator.prototype.encode = function (val) {
var transformer = new EncodingTransformer(val, this.transforms);
var references = transformer.transform();
return this.serializer.serialize(references);
};
Replicator.prototype.decode = function (val) {
var references = this.serializer.deserialize(val);
var transformer = new DecodingTransformer(references, this.transformsMap);
return transformer.transform();
};