Split objects into size-limited chunks!
Are you fed up with pesky MQTT payload size limits?
Are you ready to make big objects smaller, perhaps for the purposes of circumventing restrictive network limits?
Then this package is for you!
Overview
This package comes with two modules - one to split an object up into chunks (Splitter
), and another to re-assemble said chunks back in to the original object (Receiver
).
End-to-end example
const { Splitter, Receiver } = require('@ospin/obj-splitter')
const obj = {
a: Array(500).fill('a'),
b: Array(500).fill('b'),
}
const chunks = Splitter.split(obj, { maxChunkSize: 2500 })
console.log(chunks.length)
console.log(chunks)
In the chunks returned, the first has the key + value of the original objects a
property, and the second has b
. Together, the a
and b
properties in the original object would have exceeded the options maxChunkSize
option. Each chunk returned is under the maxChunkSize
(bytes) provided.
The multiMessage
key (which will have been added to the chunks) contains header/meta data about the chunk:
multiMessage: {
groupId: <uuidv4>,
totalChunks: <integer>,
chunkIdx: <number>,
}
Using the Receiver
, these chunks can be re-combined to form the original object:
const [ chunkA, chunkB ] = chunks
const receiver = new Receiver()
const incompleteResult = Receiver.receive(chunkA)
console.log(incompleteResult)
const completeResult = Receiver.receive(chunkB)
console.log(completeResult)
Upon receiving the second (and final) chunk, the receiver combines the chunks, removes the header data, and returns a success object. This returned payload will match the original object that was split up:
const { payload } = completeResult
JSON.stringify(payload) === JSON.stringify(obj)
Splitter.split options
The second argument in splitter.split
is an optional argument
Splitter.split(<obj>, {
maxChunkSize: <number>,
targetKey: <string>,
})
If no targetKey
is provided, the splitter will split the object's top level keys only. If a target key is provided, the splitter will split that target key up among several chunks. Each chunk will have all of the top level keys. E.g.:
const obj = {
a: 'this is a top level value!',
b: 'this is ALSO a top level value!',
data: {
nestedA: Array(500).fill('a'),
nestedB: Array(500).fill('b'),
}
}
const opts = {
maxChunkSize: 2500,
targetKey: 'data',
}
const chunks = Splitter.split(obj, opts)
console.log(chunks)
Receiver
The receiver instance will keep track of multiple series of incoming chunks:
const receiver = new Receiver({ timeout: 10000 })
const objA = { }
const [ chunkA1, chunkA2 ] = Splitter.split(objA)
const objB = { }
const [ chunkB1, chunkB2, chunkB3 ] = Splitter.split(objB)
receiver.receive(chunkA1)
console.log(receiver.chunkPools)
receiver.receive(chunkB2)
console.log(receiver.chunkPools)
...and it will remove outstanding chunk groups if there has been no chunk added to the pool in a certain amount of time:
receiver.receive(chunkB1)
console.log(receiver.chunkPools)
Notes
Q: how deep will this split an object? (e.g. will it look multiple keys down)
A: at most, this will split only 1 level deep. I.e., this will not search down the object tree for values to split.
- if no
targetKey
is provided in the options, this will split the top level object key/values only - if a
targetKey
is provided in the options, this will split the targetKey
's value up among chunks
Q: I need it to search down the object tree and split some deeply nested values?
A: This can be updated to do that without too much trouble. Get in touch, make a PR, fork it, etc.
Q: if the object I am trying to split has a multiMessage
key itself, will this overwrite it?
A: yes. there may also be other unforeseen consequences. As of initial release, there is neither test coverage nor documented expected behavior for objects that already have multiMessage
key.
Q: Why was this made?
A: To deal with AWS IoT MQTT payload limits of 128kB
Q: Can it be used for other things
A: You bet
Q: Is the space the header/metadata takes up in multiMessage
subtracted from the maxChunkSize
option?
A: yes. expect the header/metadata to reserve ~100 bytes of space for itself (it will automatically subtract its requirements from the provided maxChunkSize
when breaking up an object in to chunks)
Q: I have further questions re: the implementation
A: see the test coverage!