1- <!-- docs: sync from coderbuzz/codex@200be78 -->
1+ <!-- docs: sync from coderbuzz/codex@b37bd48 -->
22
33# Msgpack: AI Agent Knowledge File
44
1111
1212## Mental Model
1313
14- ` @coderbuzz/msgpack ` is a ** single-function library** with five exported
14+ ` @coderbuzz/msgpack ` is a ** small library** with five exported
1515functions. It maintains a single reusable internal encoder buffer to minimize
1616allocations across encode calls.
1717
@@ -95,7 +95,10 @@ send(pool.subarray(0, offset));
9595
9696** Rules:**
9797- ` offset ` defaults to ` 0 ` .
98- - No bounds checking on ` target ` : caller is responsible for buffer size.
98+ - No bounds checking on ` target ` : caller is responsible for buffer size. If the
99+ encoded bytes do not fit, ` target.set() ` throws ` RangeError ` and nothing is written.
100+ - Encodes into the internal buffer first, then copies into ` target ` . It resets the
101+ internal buffer, so it also invalidates any earlier ` encodeUnsafe ` view.
99102- Returns the number of bytes written.
100103
101104---
@@ -112,14 +115,18 @@ const restored = decode(bytes); // => { hello: "world" }
112115
113116** Rules:**
114117- ` undefined ` values round-trip as ` null ` (MessagePack has no ` undefined ` type).
118+ - ` uint64 ` (` 0xcf ` ) and ` int64 ` (` 0xd3 ` ) always decode as ` bigint ` , even for small values.
119+ - ` float32 ` (` 0xca ` ) and extension formats (` fixext ` /` ext ` , ` 0xd4 ` -` 0xd8 ` , ` 0xc7 ` -` 0xc9 ` )
120+ are not decoded: they throw "unknown format byte".
115121- Throws on unknown format byte: ` "MessagePack: unknown format byte 0xNN at offset N" ` .
116122- No bounds checking on ` data ` . Malformed input can cause out-of-bounds reads.
117123
118124---
119125
120126### ` encodedSize(value: unknown): number `
121127
122- Pre-calculates encoded size without allocating. Exact: ` encodedSize(val) === encode(val).length ` .
128+ Pre-calculates encoded size without allocating. Exact for supported types: ` encodedSize(val) === encode(val).length ` .
129+ Not exact for functions and symbols: ` encodedSize ` counts 1 byte, ` encode ` writes 0 bytes.
123130
124131``` ts
125132const size = encodedSize ({ name: " Ken" , age: 30 }); // pre-calc
@@ -182,7 +189,7 @@ All non-integer numbers use float64 (`0xcb`, 9 bytes).
182189
183190| UTF-8 Byte Length | Format | Header Size |
184191| -------------------| --------| -------------|
185- | 1 –31 | fixstr ` 0xa0\|len ` | 1 |
192+ | 0 –31 | fixstr ` 0xa0\|len ` | 1 |
186193| 32–255 | str8 ` 0xd9 ` | 2 |
187194| 256–65535 | str16 ` 0xda ` | 3 |
188195| > 65535 | str32 ` 0xdb ` | 5 |
@@ -191,7 +198,7 @@ All non-integer numbers use float64 (`0xcb`, 9 bytes).
191198
192199| Length | Format | Header Size |
193200| --------| --------| -------------|
194- | 1 –255 | bin8 ` 0xc4 ` | 2 |
201+ | 0 –255 | bin8 ` 0xc4 ` | 2 |
195202| 256–65535 | bin16 ` 0xc5 ` | 3 |
196203| > 65535 | bin32 ` 0xc6 ` | 5 |
197204
@@ -259,8 +266,8 @@ function encodeBatch(items: unknown[]): Uint8Array {
259266import { encode } from " @coderbuzz/msgpack" ;
260267
261268const data = { a: 1 , b: 2 , c: true };
262- const msgpackBytes = encode (data ); // ~10-12 bytes
263- const jsonBytes = new TextEncoder ().encode (JSON .stringify (data )); // ~18-20 bytes
269+ const msgpackBytes = encode (data ); // 10 bytes
270+ const jsonBytes = new TextEncoder ().encode (JSON .stringify (data )); // 22 bytes
264271```
265272
266273---
@@ -276,7 +283,8 @@ const jsonBytes = new TextEncoder().encode(JSON.stringify(data)); // ~18-20 byte
276283| Truncated/malformed input | Out-of-bounds read (no bounds check) |
277284| Very large array (> 2^32) | Not supported (JS limit) |
278285| ` Date ` object | Encoded as ISO string, NOT timestamp ext |
279- | ` Symbol ` , ` Map ` , ` Set ` | Not supported: will fail type check |
286+ | ` Map ` , ` Set ` , class instances | Encoded as a map of own enumerable string keys (` Map ` /` Set ` become ` {} ` ) |
287+ | ` Symbol ` , function | ** Zero bytes written** , no error. Inside an array or object this produces a corrupt stream |
280288
281289---
282290
@@ -330,37 +338,39 @@ value to encode
330338 │ ├─ Number.isInteger(v) && in varint range → smallest fixint/uint/int
331339 │ └─ else → float64 (0xcb)
332340 ├─ typeof v === "string"
333- │ ├─ len < 32 → inline UTF-8 encoder (no TextEncoder)
334- │ ├─ len < 256 → str8 (0xd9)
335- │ ├─ len < 65536 → str16 (0xda)
336- │ └─ len >= 65536 → str32 (0xdb)
341+ │ ├─ len < 32 (UTF-16 units) → inline UTF-8 encoder (no TextEncoder)
342+ │ ├─ len >= 32 → TextEncoder
343+ │ └─ header by UTF-8 byte length: < 32 fixstr, < 256 str8 (0xd9),
344+ │ < 65536 str16 (0xda), else str32 (0xdb)
337345 ├─ typeof v === "bigint"
338346 │ ├─ v >= 0n → uint64 (0xcf)
339347 │ └─ v < 0n → int64 (0xd3)
340348 ├─ v instanceof Uint8Array → bin8/16/32 based on length
341349 ├─ v instanceof Date → ISO string via .toISOString()
342350 ├─ Array.isArray(v) → fixarray/array16/32 + recursive encode
343- └─ typeof v === "object" → fixmap/map16/32 + recursive encode keys+values
351+ ├─ typeof v === "object" → fixmap/map16/32 + recursive encode keys+values
352+ └─ symbol / function → nothing written
344353```
345354
346355### Decode Decision Tree
347356
348357```
349358decode byte at position
359+ ├─ 0x00..0x7f (positive fixint) → return byte
360+ ├─ 0xe0..0xff (negative fixint) → return byte - 256
350361 ├─ 0xc0 → return null (nil)
351362 ├─ 0xc2/0xc3 → return false/true
352- ├─ 0xca → read float32 (4 bytes)
353363 ├─ 0xcb → read float64 (8 bytes)
354- ├─ 0xcc..0xcf → read uint8/16/32/64
355- ├─ 0xd0..0xd3 → read int8/16/32/64
364+ ├─ 0xcc..0xcf → read uint8/16/32/64 (uint64 returns bigint)
365+ ├─ 0xd0..0xd3 → read int8/16/32/64 (int64 returns bigint)
356366 ├─ 0xa0..0xbf (fixstr) → read string of length (byte & 0x1f)
357367 ├─ 0xd9..0xdb (str8/16/32) → read string with header length
358368 ├─ 0xc4..0xc6 (bin8/16/32) → return Uint8Array
359369 ├─ 0x90..0x9f (fixarray) → read array of length (byte & 0x0f), recurse
360370 ├─ 0xdc..0xdd (array16/32) → read array with header length, recurse
361371 ├─ 0x80..0x8f (fixmap) → read map of length (byte & 0x0f), recurse key+value
362372 ├─ 0xde..0xdf (map16/32) → read map with header length, recurse key+value
363- └─ default → throw "MessagePack: unknown format byte 0xNN at offset N"
373+ └─ default (incl. 0xca float32, ext/fixext) → throw "MessagePack: unknown format byte 0xNN at offset N"
364374```
365375
366376** ASCII fast path (decode):** Strings ≤24 bytes where all bytes are ≤ 0x7F use ` String.fromCharCode() ` directly, avoiding ` TextDecoder ` .
@@ -391,6 +401,27 @@ vs `@msgpack/msgpack`:
391401- Wire size: identical (same MessagePack spec)
392402
393403vs JSON:
394- - Encode: 4.78M ops/s faster (native, in C)
395- - Decode: 1.96M ops/s faster (native, in C)
396- - Wire size: msgpack is 35-60% smaller (no field names, compact numerics)
404+ - Encode: JSON.stringify is faster, 4.78M ops/s vs 2.04M (native)
405+ - Decode: JSON.parse is faster, 1.96M ops/s vs 0.90M (native)
406+ - Wire size: 133 bytes vs 178 for the benchmark nested object (~ 25% smaller).
407+ Field names are still written; savings come from compact headers and numerics.
408+
409+ ### Size Comparison vs JSON
410+
411+ | Payload type | JSON size | Msgpack size | Savings |
412+ | ---| ---| ---| ---|
413+ | Compact object ` { name, age, active } ` | 39 bytes | 25 bytes | ~ 36% |
414+ | Numeric array ` [1..1000] ` | 3894 bytes | 2621 bytes | ~ 33% |
415+ | Nested object (benchmark payload) | 178 bytes | 133 bytes | ~ 25% |
416+
417+ ---
418+
419+ ## Limitations
420+
421+ - No MessagePack extension types (Timestamp, custom ext). ` Date ` encodes as an ISO string.
422+ - Decoder does not read ` float32 ` (` 0xca ` ) or ` ext ` /` fixext ` ; data from other encoders
423+ that emit these throws.
424+ - No streaming decoder: the whole message must be in memory.
425+ - No bounds checking on decode: only decode trusted data.
426+ - ESM only, no CJS build (tsup ` format: ['esm'] ` , target ` es2022 ` ).
427+ - Bundle size: under 3 KB gzip.
0 commit comments