aboutsummaryrefslogtreecommitdiff
path: root/node_modules/concordance/lib/serialize.js
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-08-14 05:01:11 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-08-14 05:02:09 +0200
commit363723fc84f7b8477592e0105aeb331ec9a017af (patch)
tree29f92724f34131bac64d6a318dd7e30612e631c7 /node_modules/concordance/lib/serialize.js
parent5634e77ad96bfe1818f6b6ee70b7379652e5487f (diff)
node_modules
Diffstat (limited to 'node_modules/concordance/lib/serialize.js')
-rw-r--r--node_modules/concordance/lib/serialize.js339
1 files changed, 339 insertions, 0 deletions
diff --git a/node_modules/concordance/lib/serialize.js b/node_modules/concordance/lib/serialize.js
new file mode 100644
index 000000000..07f568415
--- /dev/null
+++ b/node_modules/concordance/lib/serialize.js
@@ -0,0 +1,339 @@
+'use strict'
+
+const md5hex = require('md5-hex')
+
+const encoder = require('./encoder')
+const pluginRegistry = require('./pluginRegistry')
+
+const argumentsValue = require('./complexValues/arguments')
+const arrayBufferValue = require('./complexValues/arrayBuffer')
+const boxedValue = require('./complexValues/boxed')
+const dataViewValue = require('./complexValues/dataView')
+const dateValue = require('./complexValues/date')
+const errorValue = require('./complexValues/error')
+const functionValue = require('./complexValues/function')
+const globalValue = require('./complexValues/global')
+const mapValue = require('./complexValues/map')
+const objectValue = require('./complexValues/object')
+const promiseValue = require('./complexValues/promise')
+const regexpValue = require('./complexValues/regexp')
+const setValue = require('./complexValues/set')
+const typedArrayValue = require('./complexValues/typedArray')
+
+const itemDescriptor = require('./metaDescriptors/item')
+const mapEntryDescriptor = require('./metaDescriptors/mapEntry')
+const pointerDescriptor = require('./metaDescriptors/pointer')
+const propertyDescriptor = require('./metaDescriptors/property')
+const statsDescriptors = require('./metaDescriptors/stats')
+
+const booleanValue = require('./primitiveValues/boolean')
+const nullValue = require('./primitiveValues/null')
+const numberValue = require('./primitiveValues/number')
+const stringValue = require('./primitiveValues/string')
+const symbolValue = require('./primitiveValues/symbol')
+const undefinedValue = require('./primitiveValues/undefined')
+
+// Increment if encoding layout, descriptor IDs, or value types change. Previous
+// Concordance versions will not be able to decode buffers generated by a newer
+// version, so changing this value will require a major version bump of
+// Concordance itself. The version is encoded as an unsigned 16 bit integer.
+const VERSION = 2
+
+// Adding or removing mappings or changing an index requires the version in
+// encoder.js to be bumped, which necessitates a major version bump of
+// Concordance itself. Indexes are hexadecimal to make reading the binary
+// output easier.
+const mappings = [
+ [0x01, booleanValue.tag, booleanValue.deserialize],
+ [0x02, nullValue.tag, nullValue.deserialize],
+ [0x03, numberValue.tag, numberValue.deserialize],
+ [0x04, stringValue.tag, stringValue.deserialize],
+ [0x05, symbolValue.tag, symbolValue.deserialize],
+ [0x06, undefinedValue.tag, undefinedValue.deserialize],
+
+ [0x07, objectValue.tag, objectValue.deserialize],
+ [0x08, statsDescriptors.iterableTag, statsDescriptors.deserializeIterableStats],
+ [0x09, statsDescriptors.listTag, statsDescriptors.deserializeListStats],
+ [0x0A, itemDescriptor.complexTag, itemDescriptor.deserializeComplex],
+ [0x0B, itemDescriptor.primitiveTag, itemDescriptor.deserializePrimitive],
+ [0x0C, statsDescriptors.propertyTag, statsDescriptors.deserializePropertyStats],
+ [0x0D, propertyDescriptor.complexTag, propertyDescriptor.deserializeComplex],
+ [0x0E, propertyDescriptor.primitiveTag, propertyDescriptor.deserializePrimitive],
+ [0x0F, pointerDescriptor.tag, pointerDescriptor.deserialize],
+
+ [0x10, mapValue.tag, mapValue.deserialize],
+ [0x11, mapEntryDescriptor.tag, mapEntryDescriptor.deserialize],
+
+ [0x12, argumentsValue.tag, argumentsValue.deserialize],
+ [0x13, arrayBufferValue.tag, arrayBufferValue.deserialize],
+ [0x14, boxedValue.tag, boxedValue.deserialize],
+ [0x15, dataViewValue.tag, dataViewValue.deserialize],
+ [0x16, dateValue.tag, dateValue.deserialize],
+ [0x17, errorValue.tag, errorValue.deserialize],
+ [0x18, functionValue.tag, functionValue.deserialize],
+ [0x19, globalValue.tag, globalValue.deserialize],
+ [0x1A, promiseValue.tag, promiseValue.deserialize],
+ [0x1B, regexpValue.tag, regexpValue.deserialize],
+ [0x1C, setValue.tag, setValue.deserialize],
+ [0x1D, typedArrayValue.tag, typedArrayValue.deserialize],
+ [0x1E, typedArrayValue.bytesTag, typedArrayValue.deserializeBytes]
+]
+const tag2id = new Map(mappings.map(mapping => [mapping[1], mapping[0]]))
+const id2deserialize = new Map(mappings.map(mapping => [mapping[0], mapping[2]]))
+
+class DescriptorSerializationError extends Error {
+ constructor (descriptor) {
+ super('Could not serialize descriptor')
+ this.name = 'DescriptorSerializationError'
+ this.descriptor = descriptor
+ }
+}
+
+class MissingPluginError extends Error {
+ constructor (pluginName) {
+ super(`Could not deserialize buffer: missing plugin ${JSON.stringify(pluginName)}`)
+ this.name = 'MissingPluginError'
+ this.pluginName = pluginName
+ }
+}
+
+class PointerLookupError extends Error {
+ constructor (index) {
+ super(`Could not deserialize buffer: pointer ${index} could not be resolved`)
+ this.name = 'PointerLookupError'
+ this.index = index
+ }
+}
+
+class UnsupportedPluginError extends Error {
+ constructor (pluginName, serializerVersion) {
+ super(`Could not deserialize buffer: plugin ${JSON.stringify(pluginName)} expects a different serialization`)
+ this.name = 'UnsupportedPluginError'
+ this.pluginName = pluginName
+ this.serializerVersion = serializerVersion
+ }
+}
+
+class UnsupportedVersion extends Error {
+ constructor (serializerVersion) {
+ super('Could not deserialize buffer: a different serialization was expected')
+ this.name = 'UnsupportedVersion'
+ this.serializerVersion = serializerVersion
+ }
+}
+
+function shallowSerializeDescriptor (descriptor, resolvePluginRef) {
+ if (!descriptor.serialize) return undefined
+
+ return serializeState(descriptor.serialize(), resolvePluginRef)
+}
+
+function serializeState (state, resolvePluginRef) {
+ if (Array.isArray(state)) return state.map(serializeState)
+
+ if (state && state.tag) {
+ let id, pluginIndex
+ if (tag2id.has(state.tag)) {
+ id = tag2id.get(state.tag)
+ pluginIndex = 0
+ } else {
+ const ref = resolvePluginRef(state.tag)
+ if (ref) {
+ id = ref.id
+ pluginIndex = ref.pluginIndex
+ }
+ }
+
+ if (id !== undefined) {
+ const serialized = [pluginIndex, id, shallowSerializeDescriptor(state, resolvePluginRef)]
+ serialized[encoder.descriptorSymbol] = true
+ return serialized
+ }
+ }
+
+ return state
+}
+
+function serialize (descriptor) {
+ const usedPlugins = new Map()
+ const resolvePluginRef = tag => {
+ const ref = pluginRegistry.resolveDescriptorRef(tag)
+ if (!ref) return null
+
+ if (!usedPlugins.has(ref.name)) {
+ // Start at 1, since 0 is reserved for Concordance's descriptors.
+ const index = usedPlugins.size + 1
+ usedPlugins.set(ref.name, Object.assign({index}, ref.serialization))
+ }
+
+ return {
+ id: ref.id,
+ pluginIndex: usedPlugins.get(ref.name).index
+ }
+ }
+
+ const seen = new Set()
+
+ const stack = []
+ let topIndex = -1
+
+ let rootRecord
+ do {
+ if (descriptor.isComplex === true) {
+ if (seen.has(descriptor.pointer)) {
+ descriptor = pointerDescriptor.describe(descriptor.pointer)
+ } else {
+ seen.add(descriptor.pointer)
+ }
+ }
+
+ let id
+ let pluginIndex = 0
+ if (tag2id.has(descriptor.tag)) {
+ id = tag2id.get(descriptor.tag)
+ } else {
+ const ref = resolvePluginRef(descriptor.tag)
+ if (!ref) throw new DescriptorSerializationError(descriptor)
+
+ id = ref.id
+ pluginIndex = ref.pluginIndex
+ }
+
+ const record = {
+ id,
+ pluginIndex,
+ children: [],
+ state: shallowSerializeDescriptor(descriptor, resolvePluginRef)
+ }
+ if (!rootRecord) {
+ rootRecord = record
+ } else {
+ stack[topIndex].children.push(record)
+ }
+
+ if (descriptor.createRecursor) {
+ stack.push({ recursor: descriptor.createRecursor(), children: record.children })
+ topIndex++
+ }
+
+ while (topIndex >= 0) {
+ descriptor = stack[topIndex].recursor()
+ if (descriptor === null) {
+ stack.pop()
+ topIndex--
+ } else {
+ break
+ }
+ }
+ } while (topIndex >= 0)
+
+ return encoder.encode(VERSION, rootRecord, usedPlugins)
+}
+exports.serialize = serialize
+
+function deserializeState (state, getDescriptorDeserializer) {
+ if (state && state[encoder.descriptorSymbol] === true) {
+ return shallowDeserializeDescriptor(state, getDescriptorDeserializer)
+ }
+
+ return Array.isArray(state)
+ ? state.map(item => deserializeState(item, getDescriptorDeserializer))
+ : state
+}
+
+function shallowDeserializeDescriptor (entry, getDescriptorDeserializer) {
+ const deserializeDescriptor = getDescriptorDeserializer(entry[0], entry[1])
+ return deserializeDescriptor(entry[2])
+}
+
+function deserializeRecord (record, getDescriptorDeserializer, buffer) {
+ const deserializeDescriptor = getDescriptorDeserializer(record.pluginIndex, record.id)
+ const state = deserializeState(record.state, getDescriptorDeserializer)
+
+ if (record.pointerAddresses.length === 0) {
+ return deserializeDescriptor(state)
+ }
+
+ const endIndex = record.pointerAddresses.length
+ let index = 0
+ const recursor = () => {
+ if (index === endIndex) return null
+
+ const recursorRecord = encoder.decodeRecord(buffer, record.pointerAddresses[index++])
+ return deserializeRecord(recursorRecord, getDescriptorDeserializer, buffer)
+ }
+
+ return deserializeDescriptor(state, recursor)
+}
+
+function buildPluginMap (buffer, options) {
+ const cache = options && options.deserializedPluginsCache
+ const cacheKey = md5hex(buffer)
+ if (cache && cache.has(cacheKey)) return cache.get(cacheKey)
+
+ const decodedPlugins = encoder.decodePlugins(buffer)
+ if (decodedPlugins.size === 0) {
+ const pluginMap = new Map()
+ if (cache) cache.set(cacheKey, pluginMap)
+ return pluginMap
+ }
+
+ const deserializerLookup = new Map()
+ if (Array.isArray(options && options.plugins)) {
+ for (const deserializer of pluginRegistry.getDeserializers(options.plugins)) {
+ deserializerLookup.set(deserializer.name, deserializer)
+ }
+ }
+
+ const pluginMap = new Map()
+ for (const index of decodedPlugins.keys()) {
+ const used = decodedPlugins.get(index)
+ const pluginName = used.name
+ const serializerVersion = used.serializerVersion
+
+ // TODO: Allow plugin author to encode a helpful message in its serialization
+ if (!deserializerLookup.has(pluginName)) {
+ throw new MissingPluginError(pluginName)
+ }
+ if (serializerVersion !== deserializerLookup.get(pluginName).serializerVersion) {
+ throw new UnsupportedPluginError(pluginName, serializerVersion)
+ }
+
+ pluginMap.set(index, deserializerLookup.get(pluginName).id2deserialize)
+ }
+
+ if (cache) cache.set(cacheKey, pluginMap)
+ return pluginMap
+}
+
+function deserialize (buffer, options) {
+ const version = encoder.extractVersion(buffer)
+ if (version !== VERSION) throw new UnsupportedVersion(version)
+
+ const decoded = encoder.decode(buffer)
+ const pluginMap = buildPluginMap(decoded.pluginBuffer, options)
+
+ const descriptorsByPointerIndex = new Map()
+ const mapPointerDescriptor = descriptor => {
+ if (descriptor.isPointer === true) {
+ if (!descriptorsByPointerIndex.has(descriptor.index)) throw new PointerLookupError(descriptor.index)
+
+ return descriptorsByPointerIndex.get(descriptor.index)
+ } else if (descriptor.isComplex === true) {
+ descriptorsByPointerIndex.set(descriptor.pointer, descriptor)
+ }
+ return descriptor
+ }
+
+ const getDescriptorDeserializer = (pluginIndex, id) => {
+ return (state, recursor) => {
+ const deserializeDescriptor = pluginIndex === 0
+ ? id2deserialize.get(id)
+ : pluginMap.get(pluginIndex).get(id)
+
+ return mapPointerDescriptor(deserializeDescriptor(state, recursor))
+ }
+ }
+ return deserializeRecord(decoded.rootRecord, getDescriptorDeserializer, buffer)
+}
+exports.deserialize = deserialize