Skip to content

Commit 89161bd

Browse files
committed
feat: optional lazy unpack wrappers (#40)
- unpack(buf, { lazy: true }) wraps maps/arrays as accessors - keep the msgpack zone and source Buffer alive with the wrapper - toJSON fully materializes; util.inspect does not hang
1 parent f9613f2 commit 89161bd

9 files changed

Lines changed: 513 additions & 19 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [3.2.0] - 2026-09-19
11+
12+
Optional second-argument unpack option `{ lazy: true }` wraps maps and arrays
13+
as accessors so nested values are not converted until they are read. See `#40`.
14+
15+
### Added
16+
17+
- `unpack(buf, { lazy: true })` keeps the decoder zone alive and returns maps
18+
as objects with accessor own-properties and arrays as array-likes with
19+
indexed accessors (`length`, `in`, `Object.keys`). Nested maps and arrays
20+
stay lazy until a property is read.
21+
- `toJSON` and `util.inspect.custom` materialize through the eager converter,
22+
so `JSON.stringify` and `util.inspect` match eager unpack. `pack()` of a
23+
lazy value also round-trips because it calls `toJSON`.
24+
- Primitives, incomplete buffers, trailing `bytes_remaining`, and the DoS
25+
limits are unchanged. `__proto__` / `constructor` stay own properties.
26+
1027
## [3.1.0] - 2026-09-19
1128

1229
Optional second-argument pack hints force a MessagePack wire type or family
@@ -108,7 +125,8 @@ GitHub Actions tests Node 18/20/22 on Ubuntu, macOS, and Windows 2022.
108125
- Pack throw paths free or return pooled sbuffers on every exit.
109126
- msgpack-c c-7.0.2 includes unpacker buffer-expansion overflow checks.
110127

111-
[Unreleased]: https://github.com/msgpack/msgpack-node/compare/v3.1.0...HEAD
128+
[Unreleased]: https://github.com/msgpack/msgpack-node/compare/v3.2.0...HEAD
129+
[3.2.0]: https://github.com/msgpack/msgpack-node/compare/v3.1.0...v3.2.0
112130
[3.1.0]: https://github.com/msgpack/msgpack-node/compare/v3.0.0...v3.1.0
113131
[3.0.0]: https://github.com/msgpack/msgpack-node/compare/e04c9b55f98d64512174d6e859b8294b729659a2...HEAD
114132
[2.0.0]: https://github.com/msgpack/msgpack-node/commit/e04c9b55f98d64512174d6e859b8294b729659a2

‎COVERAGE.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Coverage — msgpack 3.1.0
1+
# Coverage — msgpack 3.2.0
22

33
`npm run coverage` runs both halves and fails the build under 95%.
44

@@ -8,9 +8,9 @@
88
| `lib/` + `bin/` (c8) | branches | **100%** | ≥ 95% |
99
| `lib/` + `bin/` (c8) | functions | **100%** | ≥ 95% |
1010
| `lib/` + `bin/` (c8) | lines | **100%** | ≥ 95% |
11-
| `src/` (gcovr) | lines | **95.2%** (902/947) | ≥ 95% |
12-
| `src/` (gcovr) | branches | **95.4%** (836/876) | ≥ 95% |
13-
| `src/` (gcovr) | functions | 100% (59/59) | — |
11+
| `src/` (gcovr) | lines | **95.7%** (1002/1047) | ≥ 95% |
12+
| `src/` (gcovr) | branches | **95.5%** (976/1022) | ≥ 95% |
13+
| `src/` (gcovr) | functions | 100% (66/66) | — |
1414

1515
`deps/` is excluded from the native report; the vendored msgpack-c is not our
1616
code. `build/` is rebuilt without instrumentation at the end of
@@ -146,6 +146,13 @@ gcovr --root . --filter src/ --exclude deps/ --no-markers --txt-metric branch --
146146
`kMaxPackDepth`) are marked `GCOVR_EXCL_*`, not deleted. Native overall
147147
stays above the 95% gate (`pack_hints.inc` itself is 91% branches because
148148
switch `default:` edges sit on the same line as covered cases).
149+
- `test/lazy.test.js` — `#40` `unpack(buf, { lazy: true })`: one-arg eager
150+
identity, nested `o.c[1]` without reading siblings, `__proto__` /
151+
`constructor` as own properties, oversized headers still throw, incomplete
152+
buffers still return `null`, `toJSON` / `JSON.stringify` / `util.inspect`
153+
match eager unpack, nested BigInt, non-object second args, and toJSON
154+
`this` checks. Lazy OOM / empty-Maybe / ObjectTemplate-failure arms are
155+
marked `GCOVR_EXCL_*`, not deleted.
149156
- `test/cli.test.js` (12 tests) — the exit-1 paths of both CLIs: invalid JSON,
150157
empty stdin, a pack rejection reachable from real JSON, an unparseable byte,
151158
an oversized header, incomplete input both alone and after a good frame, and

‎README.md‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
and de-serializes JavaScript values with [MessagePack](https://msgpack.org).
33
Packed output is a `Buffer` and is typically much smaller than JSON.
44

5-
Version 3.1 requires **Node.js 18+**, vendors **msgpack-c c-7.0.2**, unpacks
6-
64-bit integers outside `Number.MAX_SAFE_INTEGER` as `bigint`, and accepts
7-
optional pack type/family hints. See [`SECURITY.md`](SECURITY.md).
5+
Version 3.2 requires **Node.js 18+**, vendors **msgpack-c c-7.0.2**, unpacks
6+
64-bit integers outside `Number.MAX_SAFE_INTEGER` as `bigint`, accepts
7+
optional pack type/family hints, and can unpack maps and arrays lazily
8+
(`unpack(buf, { lazy: true })`). See [`SECURITY.md`](SECURITY.md).
89

910
### Usage
1011

@@ -79,6 +80,16 @@ is that same `bigint`.
7980
`unpack.bytes_remaining` is the number of unused trailing bytes after the last
8081
successful (or attempted) unpack. Stream uses that to splice leftover data.
8182

83+
`unpack(buf, { lazy: true })` wraps maps as objects with accessor
84+
own-properties and arrays as array-likes with indexed accessors. Nested
85+
values are not converted until they are read, which is useful for large
86+
payloads when only a few keys are needed. `JSON.stringify` and
87+
`util.inspect` materialize via `toJSON` / `inspect.custom`. Lazy arrays are
88+
not real `Array`s (`Array.isArray` is false); `pack()` still round-trips
89+
them because it calls `toJSON`. Primitives unpack eagerly even when `lazy`
90+
is set. `__proto__` and `constructor` keys stay own properties, same as
91+
eager unpack.
92+
8293
### Pack type hints (3.1)
8394

8495
`pack(value, options)` takes an optional last-argument options object when

‎index.d.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Type definitions for msgpack 3.1.0
1+
// Type definitions for msgpack 3.2.0
22
// Project: https://github.com/msgpack/msgpack-node
33

44
/// <reference types="node" />
@@ -74,8 +74,15 @@ export function pack(...values: any[]): Buffer;
7474
* Returns `null` when the buffer holds an incomplete value, in which case
7575
* `unpack.bytes_remaining` equals `buf.length`. Throws on malformed input or
7676
* when a container/string/bin header exceeds the decoder's limits.
77+
*
78+
* Pass `{ lazy: true }` to wrap maps as objects with accessor own-properties
79+
* and arrays as array-likes with indexed accessors. Nested values are not
80+
* converted until read. `JSON.stringify` and `util.inspect` materialize via
81+
* `toJSON` / `inspect.custom`. Lazy arrays are not real `Array`s
82+
* (`Array.isArray` is false); `pack()` still round-trips them because it
83+
* calls `toJSON`. Primitives unpack eagerly even when `lazy` is set.
7784
*/
78-
export function unpack(buf: Buffer): any;
85+
export function unpack(buf: Buffer, opts?: { lazy?: boolean }): any;
7986

8087
export namespace unpack {
8188
/**

‎lib/msgpack.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ function pack() {
1818
return bpack.apply(null, arguments);
1919
}
2020

21-
function unpack(buf) {
22-
const result = rawUnpack(buf);
21+
function unpack(buf, opts) {
22+
const result = arguments.length < 2 ? rawUnpack(buf) : rawUnpack(buf, opts);
2323
unpack.bytes_remaining = mpBindings.bytesRemaining();
2424
return result;
2525
}

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "msgpack",
33
"description": "A space-efficient object serialization library for Node.js",
4-
"version": "3.1.0",
4+
"version": "3.2.0",
55
"homepage": "https://github.com/msgpack/msgpack-node",
66
"author": "Peter Griess <pg@std.in>",
77
"contributors": [
@@ -34,7 +34,7 @@
3434
"nan": "^2.23.1"
3535
},
3636
"scripts": {
37-
"test": "node --test test/bigint.test.js test/cli.test.js test/coverage-native.test.js test/msgpack.test.js test/pack-hints.test.js test/regression.test.js test/security.test.js test/worker.test.js",
37+
"test": "node --test test/bigint.test.js test/cli.test.js test/coverage-native.test.js test/lazy.test.js test/msgpack.test.js test/pack-hints.test.js test/regression.test.js test/security.test.js test/worker.test.js",
3838
"bench": "node test/benchmark/benchmark.js",
3939
"rebuild": "node-gyp rebuild",
4040
"coverage": "npm run coverage:js && npm run coverage:native",

0 commit comments

Comments
 (0)