All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
3.4.0 - 2026-09-19
Pack bigint values outside the 64-bit integer range as MessagePack ext
type 0x42 (msgpackr useBigIntExtension two's-complement payload), and
unpack that type back to bigint. Payload is capped at 256 bytes. Values
that still fit int64/uint64 keep using integer wire.
- Out-of-range
bigintpacks as ext0x42and round-trips throughunpack(), including nested arrays/maps/objects, uint256, and the 64-bit boundaries2^64and-2^63-1. - Unpack of ext
0x42with a payload larger than 256 bytes throwscannot unpack BigInt: ext payload exceeds 256 bytes. Other ext types still throwcannot unpack ext type.
cannot pack BigInt outside 64-bit rangeis no longer thrown for values that fit in a 256-byte ext payload. Larger values throwcannot pack BigInt: ext payload exceeds 256 bytes.
3.3.0 - 2026-09-19
Stream.send queues packed messages when the underlying writable returns
false, re-emits drain, and refuses more than 1024 pending messages.
See #43.
Streamre-emitsdrainfrom the underlying writable so callers can listen on the msgpack Stream, not only on the raw socket.- After
write()returnsfalse, furthersend()calls queue the already packed Buffer and flush FIFO ondrain.send()stays synchronous and returns the boolean fromwrite(), orfalseif the message was queued. - Extra arguments (encoding, callback) are still forwarded to
writeon an immediate write. Queued flushes callwrite(buf)without inventing an encoding; a callback supplied on a queuedsendruns after that buffer is written, or with an error if the queue is dropped. - The pending-send queue is capped at 1024 messages. A further
send()throws a catchableErrorwhose message mentions backpressure / queue full. - If the underlying stream emits
error,close, orendwith messages still queued, the queue is dropped and Stream emitserror. An empty queue does not emit that extra error. Handlers do not throw.
3.2.0 - 2026-09-19
Optional second-argument unpack option { lazy: true } wraps maps and arrays
as accessors so nested values are not converted until they are read. See #40.
unpack(buf, { lazy: true })keeps the decoder zone alive and returns maps as objects with accessor own-properties and arrays as array-likes with indexed accessors (length,in,Object.keys). Nested maps and arrays stay lazy until a property is read.toJSONandutil.inspect.custommaterialize through the eager converter, soJSON.stringifyandutil.inspectmatch eager unpack.pack()of a lazy value also round-trips because it callstoJSON.- Primitives, incomplete buffers, trailing
bytes_remaining, and the DoS limits are unchanged.__proto__/constructorstay own properties. - Lazy unpack copies the input before decode so str/bin do not alias the caller's Buffer. Transferring that Buffer after unpack cannot dangle later property reads.
3.1.0 - 2026-09-19
Optional second-argument pack hints force a MessagePack wire type or family
without changing the default mapping. Two or more values still pack as an
array. See #52.
pack(value, { type })writes a fixed MessagePack type (fixint,uint8…uint64,int8…int64,float32/float64,fixstr/str8…str32,bin8…bin32,nil/true/false). Out-of-range values throwcannot pack value as <type>.pack(value, { family })picks a compact encoding in that family (int,float,str,bin).typewins if both are set.pack(array, { interpret })maps each element throughinterpret(item)which must return{ data }and may also settype/family.- Detection is last-argument, two-arg only: the object must own-enumerate
only
type,family, and/orinterpret. Extra keys, one-arg objects, andpack(1, 2)keep the old array packing.
3.0.0 - 2026-09-19
Integers whose magnitude is greater than Number.MAX_SAFE_INTEGER unpack as
bigint instead of a rounded number. Values that fit stay number
regardless of wire width. pack() accepts bigint in the signed/unsigned
64-bit range.
pack()encodesbigintviav8::BigIntInt64Value/Uint64Valueas the smallest MessagePack integer family that fits.- Unpack of uint64/int64 values outside
Number.MAX_SAFE_INTEGERreturnsbigintso 64-bit integers stay exact (#37).
- A uint64 of
1still unpacks as Number1.Number.MAX_SAFE_INTEGERstays Number even when the wire type is uint64. - A JS
numberthat is already rounded (for example18446464814936021000) still packs on the Number path; lost bits are not recovered.
- Unpacking a 64-bit integer larger than
Number.MAX_SAFE_INTEGERnow returnsbigintinstead of the nearest double. Code that assumedtypeof unpack(...) === 'number'for every integer must acceptbigint. pack(10n)no longer throwscannot pack object. BigInt outside uint64/int64 (2n ** 64n,-(2n ** 63n) - 1n) throwscannot pack BigInt outside 64-bit range.
2.0.0 - 2026-09-18
Security modernization. Requires Node.js 18+. Vendors msgpack-c c-7.0.2. GitHub Actions tests Node 18/20/22 on Ubuntu, macOS, and Windows 2022.
- Fail-closed unpack limits: array/map length ≤ 1,000,000, str/bin/ext ≤ 32 MiB,
nesting depth ≤ 512. The bomb
dd ff 00 00 00throws instead of allocating. - Pack recursion cap of 512 (deep input throws instead of SIGSEGV).
Streamemitserroron unpack throw and drops the offending buffer.worker_threadssupport (NAN_MODULE_WORKER_ENABLED, thread-local sbuffer pool andbytes_remaining).- TypeScript types (
index.d.ts). node:testsuite, c8 + gcov coverage gated at 95%.SECURITY.mdandCOVERAGE.md.
nanis^2.23.1(installs 2.x current).binding.gypno longer pins-std=c++11.- node-gyp 10+ uses Python 3.
- Dates pack as ISO-8601 strings (
toISOString()) at every nesting level. - Objects with
toJSON()use that return value at every nesting level. - Numeric own keys are packed instead of dropped.
- Cycle marks use V8 private symbols so a user key named
_msgpack_stackis kept. - Integral doubles outside uint64/int64 range (for example
1e30) pack as float64. - Map keys are installed with
DefineOwnPropertyso a wire__proto__cannot replace the decoded object's prototype. - Property reads during pack go through
Nan::TryCatch(throwing getters and Proxy traps raise a catchable error). Streamsnapshotsbytes_remainingand consumes the frame beforeemit('msg'), so a listener that unpacks or throws cannot desync or replay.Streamemits packed integer0and packednullas real messages.
- Top-level
Bufferpacks as MessagePack bin, notBuffer.prototype.toJSON's{type, data}map (#49). - sbuffer leak on pack throw (
#25686). - Python msgpack maps with bin8 payloads unpack (
#10). Streamno longer skips packed0(#44).
- Unpacker rejects oversized headers before the C decoder allocates.
- Pack throw paths free or return pooled sbuffers on every exit.
- msgpack-c c-7.0.2 includes unpacker buffer-expansion overflow checks.