Skip to content

Commit 7ecaab1

Browse files
docs: sync from coderbuzz/codex@b37bd48
1 parent a35e666 commit 7ecaab1

2 files changed

Lines changed: 64 additions & 32 deletions

File tree

‎AI_KNOWLEDGE.md‎

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- docs: sync from coderbuzz/codex@200be78 -->
1+
<!-- docs: sync from coderbuzz/codex@b37bd48 -->
22

33
# Msgpack: AI Agent Knowledge File
44

@@ -11,7 +11,7 @@
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
1515
functions. It maintains a single reusable internal encoder buffer to minimize
1616
allocations 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
125132
const 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 {
259266
import { encode } from "@coderbuzz/msgpack";
260267

261268
const 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
```
349358
decode 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

393403
vs 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.

‎README.md‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
<!-- docs: sync from coderbuzz/codex@200be78 -->
1+
<!-- docs: sync from coderbuzz/codex@b37bd48 -->
22

33
# Msgpack: `@coderbuzz/msgpack`
44

5-
> **High-performance MessagePack for TypeScript.** Smaller than JSON. 2x faster than `@msgpack/msgpack`. Zero unnecessary allocations.
5+
> **High-performance MessagePack for TypeScript.** Smaller than JSON. 2.7x faster encode than `@msgpack/msgpack`. Zero unnecessary allocations.
66
> AI agents: see [AI_KNOWLEDGE.md](https://github.com/coderbuzz/msgpack/blob/main/AI_KNOWLEDGE.md) for expert context.
77
<p align="center">
88
<a href="https://www.npmjs.com/package/@coderbuzz/msgpack"><img src="https://img.shields.io/npm/v/@coderbuzz/msgpack.svg?style=flat-square" alt="npm version" /></a>
@@ -13,7 +13,7 @@
1313
<a href="https://codecov.io/gh/coderbuzz/msgpack"><img src="https://codecov.io/gh/coderbuzz/msgpack/graph/badge.svg" alt="Codecov" /></a>
1414
</p>
1515
16-
`@coderbuzz/msgpack` is a purpose-built MessagePack encoder/decoder optimized for minimal GC pressure and maximum throughput. For structured API responses, compact objects are **~55% smaller** than JSON, and numeric arrays are **~60% smaller**.
16+
`@coderbuzz/msgpack` is a purpose-built MessagePack encoder/decoder optimized for minimal GC pressure and maximum throughput. Compact objects are **~35% smaller** than JSON, and small-integer arrays are **~33% smaller**.
1717

1818
---
1919

@@ -48,16 +48,16 @@
4848

4949
| Payload type | JSON size | Msgpack size | Savings |
5050
|---|---|---|---|
51-
| Compact object `{ name, age, active }` | ~45 bytes | ~20 bytes | **~55%** |
52-
| Numeric array `[1..1000]` | ~3.9 KB | ~1.5 KB | **~60%** |
53-
| Structured API response (nested) | ~2 KB | ~1.3 KB | **~35%** |
51+
| Compact object `{ name, age, active }` | 39 bytes | 25 bytes | **~36%** |
52+
| Numeric array `[1..1000]` | 3894 bytes | 2621 bytes | **~33%** |
53+
| Nested object (benchmark payload) | 178 bytes | 133 bytes | **~25%** |
5454

5555
### Throughput & Wire Size (Apple M-series, Bun)
5656

5757
Full results at **[github.com/coderbuzz/benchmarks](https://github.com/coderbuzz/benchmarks)**.
5858

5959
| Scenario | @coderbuzz/msgpack | @msgpack/msgpack | Factor |
60-
|---|---|---|---|---|
60+
|---|---|---|---|
6161
| Nested object encode | **2.04M ops/s** | 0.77M | **2.7x** |
6262
| Nested object decode | **0.90M ops/s** | 0.87M | **1.04x** |
6363
| Wire size (nested object) | **133 bytes** | 133 bytes | Same |
@@ -84,10 +84,10 @@ import { encode, decode } from "npm:@coderbuzz/msgpack";
8484
## Quick Start
8585
8686
```ts
87-
import { decode, encode } from "@coderbuzz/msgpack";
87+
import { decode, encode, encodedSize, encodeInto, encodeUnsafe } from "@coderbuzz/msgpack";
8888

8989
const bytes = encode({ name: "Alice", age: 30, active: true });
90-
// => Uint8Array (compact binary, ~20 bytes vs ~45 bytes JSON)
90+
// => Uint8Array (compact binary, 25 bytes vs 39 bytes JSON)
9191

9292
const value = decode(bytes);
9393
// => { name: "Alice", age: 30, active: true }
@@ -171,7 +171,7 @@ const buffer = new Uint8Array(size);
171171
encodeInto({ name: "Alice", age: 30, scores: [1, 2, 3] }, buffer);
172172
```
173173
174-
`encodedSize(val) === encode(val).length` always holds.
174+
`encodedSize(val) === encode(val).length` holds for the supported types above. It does not hold for functions and symbols, which `encode` writes as zero bytes.
175175
176176
---
177177
@@ -196,7 +196,7 @@ encodeInto({ name: "Alice", age: 30, scores: [1, 2, 3] }, buffer);
196196
197197
| Byte Length | Format | Header Size |
198198
|---|---|---|
199-
| 1–31 | fixstr | 1 byte |
199+
| 0–31 | fixstr | 1 byte |
200200
| 32–255 | str8 | 2 bytes |
201201
| 256–65535 | str16 | 3 bytes |
202202
| > 65535 | str32 | 5 bytes |
@@ -251,6 +251,7 @@ function batchEncode(items: unknown[]): Uint8Array {
251251
## Limitations
252252
253253
- **No MessagePack extension types**: Timestamp, custom extensions not supported. `Date` objects are ISO strings.
254+
- **Decoder reads only what the encoder writes**: `float32` (`0xca`), `fixext`/`ext` (`0xd4`-`0xd8`, `0xc7`-`0xc9`) throw "unknown format byte". Data from encoders that emit float32 will not decode.
254255
- **No streaming/SAX decoder**: Entire message in memory.
255256
- **No bounds checking on decode**: Only decode trusted data.
256257
- **No CJS build**: ESM only. Node.js 18+ with `"type": "module"`.

0 commit comments

Comments
 (0)