|
|
|
"use strict";
|
|
|
|
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
|
|
value: true
|
|
|
|
});
|
|
|
|
exports.registerSerializableClass = registerSerializableClass;
|
|
|
|
exports.unregisterSerializableClass = unregisterSerializableClass;
|
|
|
|
exports.prepareForSerialization = prepareForSerialization;
|
|
|
|
exports.restoreDeserializedObject = restoreDeserializedObject;
|
|
|
|
exports.serialize = serialize;
|
|
|
|
exports.deserialize = deserialize;
|
|
|
|
exports.cacheSerializedObject = cacheSerializedObject;
|
|
|
|
exports.deserializeToCache = deserializeToCache;
|
|
|
|
exports.removeSerializedObjectFromCache = removeSerializedObjectFromCache;
|
|
|
|
exports.deserializeRaw = exports.serializeRaw = void 0;
|
|
|
|
|
|
|
|
function _v() {
|
|
|
|
const data = _interopRequireDefault(require("v8"));
|
|
|
|
|
|
|
|
_v = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
var _buildCache = require("./buildCache");
|
|
|
|
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
|
|
|
|
// $FlowFixMe - Flow doesn't know about this method yet
|
|
|
|
let serializeRaw = _v().default.serialize; // $FlowFixMe - Flow doesn't know about this method yet
|
|
|
|
|
|
|
|
|
|
|
|
exports.serializeRaw = serializeRaw;
|
|
|
|
|
|
|
|
let deserializeRaw = _v().default.deserialize;
|
|
|
|
|
|
|
|
exports.deserializeRaw = deserializeRaw;
|
|
|
|
const nameToCtor = new Map();
|
|
|
|
const ctorToName = new Map();
|
|
|
|
|
|
|
|
function registerSerializableClass(name, ctor) {
|
|
|
|
if (ctorToName.has(ctor)) {
|
|
|
|
throw new Error('Class already registered with serializer');
|
|
|
|
}
|
|
|
|
|
|
|
|
nameToCtor.set(name, ctor);
|
|
|
|
ctorToName.set(ctor, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
function unregisterSerializableClass(name, ctor) {
|
|
|
|
if (nameToCtor.get(name) === ctor) {
|
|
|
|
nameToCtor.delete(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctorToName.get(ctor) === name) {
|
|
|
|
ctorToName.delete(ctor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function shallowCopy(object) {
|
|
|
|
if (object && typeof object === 'object') {
|
|
|
|
if (Array.isArray(object)) {
|
|
|
|
return [...object];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (object instanceof Map) {
|
|
|
|
return new Map(object);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (object instanceof Set) {
|
|
|
|
return new Set(object);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Object.create(Object.getPrototypeOf(object), Object.getOwnPropertyDescriptors(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isBuffer(object) {
|
|
|
|
return object.buffer instanceof ArrayBuffer || object.buffer instanceof SharedArrayBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
function shouldContinueMapping(value) {
|
|
|
|
return value && typeof value === 'object' && value.$$raw !== true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapObject(object, fn, preOrder = false) {
|
|
|
|
let cache = new Map();
|
|
|
|
let memo = new Map(); // Memoize the passed function to ensure it always returns the exact same
|
|
|
|
// output by reference for the same input. This is important to maintain
|
|
|
|
// reference integrity when deserializing rather than cloning.
|
|
|
|
|
|
|
|
let memoizedFn = val => {
|
|
|
|
let res = memo.get(val);
|
|
|
|
|
|
|
|
if (res == null) {
|
|
|
|
res = fn(val);
|
|
|
|
memo.set(val, res);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
};
|
|
|
|
|
|
|
|
let walk = (object, shouldCopy = false) => {
|
|
|
|
// Check the cache first, both for performance and cycle detection.
|
|
|
|
if (cache.has(object)) {
|
|
|
|
return cache.get(object);
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = object;
|
|
|
|
cache.set(object, result);
|
|
|
|
|
|
|
|
let processKey = (key, value) => {
|
|
|
|
let newValue = value;
|
|
|
|
|
|
|
|
if (preOrder && value && typeof value === 'object') {
|
|
|
|
newValue = memoizedFn(value);
|
|
|
|
} // Recursively walk the children
|
|
|
|
|
|
|
|
|
|
|
|
if (preOrder ? shouldContinueMapping(newValue) : newValue && typeof newValue === 'object' && shouldContinueMapping(object)) {
|
|
|
|
newValue = walk(newValue, newValue === value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!preOrder && newValue && typeof newValue === 'object') {
|
|
|
|
newValue = memoizedFn(newValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newValue !== value) {
|
|
|
|
// Copy on write. We only need to do this when serializing, not deserializing.
|
|
|
|
if (object === result && preOrder && shouldCopy) {
|
|
|
|
result = shallowCopy(object);
|
|
|
|
cache.set(object, result);
|
|
|
|
} // Replace the key with the new value
|
|
|
|
|
|
|
|
|
|
|
|
if (result instanceof Map) {
|
|
|
|
result.set(key, newValue);
|
|
|
|
} else if (result instanceof Set) {
|
|
|
|
let _result = result; // For Flow
|
|
|
|
// TODO: do we care about iteration order??
|
|
|
|
|
|
|
|
_result.delete(value);
|
|
|
|
|
|
|
|
_result.add(newValue);
|
|
|
|
} else {
|
|
|
|
result[key] = newValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}; // Iterate in various ways depending on type.
|
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(object)) {
|
|
|
|
for (let i = 0; i < object.length; i++) {
|
|
|
|
processKey(i, object[i]);
|
|
|
|
}
|
|
|
|
} else if (object instanceof Map || object instanceof Set) {
|
|
|
|
for (let [key, val] of object.entries()) {
|
|
|
|
processKey(key, val);
|
|
|
|
}
|
|
|
|
} else if (!isBuffer(object)) {
|
|
|
|
for (let key in object) {
|
|
|
|
processKey(key, object[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
let mapped = memoizedFn(object);
|
|
|
|
|
|
|
|
if (preOrder ? shouldContinueMapping(mapped) : mapped && typeof mapped === 'object' && shouldContinueMapping(object)) {
|
|
|
|
return walk(mapped, mapped === object);
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapped;
|
|
|
|
}
|
|
|
|
|
|
|
|
function prepareForSerialization(object) {
|
|
|
|
if (object !== null && object !== void 0 && object.$$raw) {
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapObject(object, value => {
|
|
|
|
// Add a $$type property with the name of this class, if any is registered.
|
|
|
|
if (value && typeof value === 'object' && typeof value.constructor === 'function') {
|
|
|
|
let type = ctorToName.get(value.constructor);
|
|
|
|
|
|
|
|
if (type != null) {
|
|
|
|
let serialized = value;
|
|
|
|
let raw = false;
|
|
|
|
|
|
|
|
if (value && typeof value.serialize === 'function') {
|
|
|
|
var _ref;
|
|
|
|
|
|
|
|
// If the object has a serialize method, call it
|
|
|
|
serialized = value.serialize();
|
|
|
|
raw = (_ref = serialized && serialized.$$raw) !== null && _ref !== void 0 ? _ref : true;
|
|
|
|
|
|
|
|
if (serialized) {
|
|
|
|
delete serialized.$$raw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
$$type: type,
|
|
|
|
$$raw: raw,
|
|
|
|
value: { ...serialized
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function restoreDeserializedObject(object) {
|
|
|
|
return mapObject(object, value => {
|
|
|
|
// If the value has a $$type property, use it to restore the object type
|
|
|
|
if (value && value.$$type) {
|
|
|
|
let ctor = nameToCtor.get(value.$$type);
|
|
|
|
|
|
|
|
if (ctor == null) {
|
|
|
|
throw new Error(`Expected constructor ${value.$$type} to be registered with serializer to deserialize`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof ctor.deserialize === 'function') {
|
|
|
|
return ctor.deserialize(value.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
value = value.value;
|
|
|
|
Object.setPrototypeOf(value, ctor.prototype);
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const serializeCache = (0, _buildCache.createBuildCache)();
|
|
|
|
|
|
|
|
function serialize(object) {
|
|
|
|
let cached = serializeCache.get(object);
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mapped = prepareForSerialization(object);
|
|
|
|
return serializeRaw(mapped);
|
|
|
|
}
|
|
|
|
|
|
|
|
function deserialize(buffer) {
|
|
|
|
let obj = deserializeRaw(buffer);
|
|
|
|
return restoreDeserializedObject(obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
function cacheSerializedObject(object, buffer) {
|
|
|
|
serializeCache.set(object, buffer || serialize(object));
|
|
|
|
}
|
|
|
|
|
|
|
|
function deserializeToCache(buffer) {
|
|
|
|
let deserialized = deserialize(buffer);
|
|
|
|
serializeCache.set(deserialized, buffer);
|
|
|
|
return deserialized;
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeSerializedObjectFromCache(object) {
|
|
|
|
serializeCache.delete(object);
|
|
|
|
}
|