Skip to content

About

Fast, zero-dependency schema-based compact serialization for Node.js and browsers: objects travel as positional JSON arrays, a shared schema maps every position back to its key. A lightweight alternative to Protocol Buffers

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

protoarray

npm CI node dependencies docs license

Schema-based serialization that stays JSON. Send objects as positional arrays and let a schema shared by the client and the server map every position back to its key: the discipline of Protocol Buffers over the transport every JavaScript program already has. A fast, zero-dependency library for Node.js and browsers: about half the bytes of JSON, faster than JSON, and still JSON. Schemas are @alexify/metaschema's: one definition validates with metaschema and travels with protoarray.

const { Schema } = require('@alexify/protoarray');
const { Schema: Definition } = require('@alexify/metaschema');

const User = Schema.from(
  new Definition('User', {
    id: 'number',
    name: 'string',
    role: { enum: ['admin', 'editor', 'viewer'] },
    address: { city: 'string', zip: '?string' },
    tags: { array: 'string' },
    email: '?string',
  }),
);

User.encode({ id: 1, name: 'Alex', role: 'editor', address: { city: 'Kyiv' }, tags: ['a'] });
// [1, 'Alex', 1, ['Kyiv'], ['a']]

User.decode([1, 'Alex', 1, ['Kyiv'], ['a']]);
// { id: 1, name: 'Alex', role: 'editor', address: { city: 'Kyiv', zip: null }, tags: ['a'], email: null }

User.stringify(user); // '[1,"Alex",1,["Kyiv"],["a"]]'  β€” JSON.stringify of the array
User.parse(text);     // the object back, the wire checked

Both sides share the schema, so the keys never travel: position 0 is id, position 1 is name, and so on. The enum travels as its index, missing optional fields as null, and trailing nulls are trimmed.

The client does not need metaschema at all. A schema's snapshot is plain JSON, and protoarray builds the same schema from it:

// The server
reply.send(User.snapshot);

// The client: protoarray alone, about 22 KB
const User = Schema.from(await (await fetch('/schemas/user')).json());

Why protoarray

  • Smaller payloads. Keys are not repeated in every message, only the values travel: about half the bytes of JSON on a typical record before compression, and still 5–35% fewer after gzip depending on the message size; 27% fewer on a list with the columns layout, the ids as differences and the repeated strings interned.
  • Fast as a binary codec. A positional array is less work for JSON.parse and JSON.stringify, and the object is rebuilt by generated code with one hidden class per schema: Γ—1.5 encode and Γ—1.8 decode against JSON on a typical record. On records, strings and tagged messages it encodes faster than protobufjs, protobuf-es, avsc, msgpackr and cbor-x, and it has the fastest round trip of one record; msgpackr with shared structures decodes faster (and avsc, cbor-x or protobufjs on some shapes), and the binary codecs win on mostly numeric payloads (see benchmarks). Where a channel carries bytes, @alexify/protoarray/binary puts the same schema on a binary wire of its own: Γ—1.6 encode and Γ—2.4 decode against JSON on that record, and the fastest round trip of every library on all seven payloads of the comparison, the numeric one included.
  • Still JSON. The payload is a plain JSON array, so it goes through any transport and storage that already handle JSON, compresses with gzip, brotli and zstd like JSON, and stays readable in logs.
  • One schema, two jobs. No .proto files, no compiler, no generated files: a schema is a metaschema definition, a plain JavaScript value. metaschema validates the values with it, protoarray carries them, and both read a reference, an optional field or a stored entity's id the same way.
  • Evolves safely. Append fields at the end and old and new versions keep understanding each other; a fingerprint (schema.id, 32 or 64 bits) tells versions apart in a handshake, and protoarray check holds a module to the rules in CI, against the baseline that shipped. For changes that break the rules, resolve reads and writes another version by name, Avro-style, and a registry wraps payloads as [id, payload].
  • Typed. A schema takes its TypeScript types from the metaschema schema it is built from; hand-written typings for TypeScript 5.0 or newer.
  • Zero dependencies, about 22 KB min+gzip. protoarray requires nothing, metaschema included: it recognises a metaschema schema by its brand and reads its snapshot, and takes the metaschema module as an option ({ metaschema }) only to read a raw definition. CommonJS with ESM named imports, no build step, one package for Node.js 18+ and browsers; the registry, resolve, streaming and evolve extras are subpaths of 1.7–3.8 KB each, the binary wire and the standalone generator ones of about 9 KB. Generated code where the runtime allows it, and under a strict CSP or on edge runtimes either a closures fallback or a standalone module generated at build time, carrying both wires.

Installation

pnpm add @alexify/protoarray @alexify/metaschema
# or
npm install @alexify/protoarray @alexify/metaschema

Requires Node.js 18 or newer. Works in browsers through any bundler. metaschema (3.4 or newer) is needed where schemas are defined; a client that builds its schemas from snapshots installs protoarray alone.

Features

Feature How
Schemas metaschema's: Schema.from(new Definition(name, definition)), Schema.from(definition, { metaschema }), Schema.from(snapshot), Schema.fromModel(model)
Scalars string, number, integer, boolean, bigint (a decimal string), date (epoch ms), bytes (base64), json, any, unknown, null
Optional fields '?string', 'email?': 'string', required: false, nullable: true; missing ones decode as null, trailing nulls are trimmed
Defaults { type: 'number', default: 0 } fills a missing slot on decode
Nested records address: { city: 'string' } β†’ a nested array
Collections { array: 'string' } travels as is; { array: Record } becomes an array of arrays; User.list; { set }, { map } and { object } dictionaries; tuples of any elements
Compact types { enum: [...] } β†’ index, { flags: [...] } β†’ bitmask, { type: 'number', decimals: 2 } β†’ scaled integer, bytes β†’ base64
Unions { union: [Call, Event], discriminator: 'type' } β†’ [tag, ...fields]
References as metaschema's check reads them: a stored entity as its id, a memory kind as its record, embed and references to choose; recursion
Sparse records { sparse: true } or { sparse: ['profile'] } β†’ [mask, ...present values] for records with many optional fields
Layouts { layout: 'flat' } (one array, fixed stride) or 'columns' (transposed, best for compression); { layout: { items: 'columns' } } for a collection inside a record
Delta and interning { delta: ['ids'] } writes integers as differences (a column, or a list); { intern: ['city'] } writes strings of few values as indexes into one table per message
Migrations resolve(writer, reader) (@alexify/protoarray/resolve) reads and writes another version by name, with aliases; SchemaRegistry keeps versions by id and wraps payloads as [id, payload]
Streaming encodeEach/decodeEach; NDJSON, TransformStreams and the items of a large array read as they arrive (@alexify/protoarray/stream)
Standalone modules protoarray compile writes the generated code of your schemas into a module, for a strict CSP and edge runtimes, with no metaschema at runtime, the binary wire too with --binary (@alexify/protoarray/standalone)
Binary wire toBinary(schema, value) and fromBinary(schema, bytes) put the same schema on bytes: a header of presence bits, varints, float64s, UTF-16 for long strings; for channels that carry bytes and numeric payloads (@alexify/protoarray/binary)
Validation the wire on decode by default: DecodeError with codes, paths like orders[1].sku and the schema name; the values before encode with metaschema's check and parse
Evolution append-only positions; { strict: true } rejects elements beyond the schema; schema.id tells versions apart, 32 bits by default, { fingerprint: 'fnv1a64' } for a registry of many schemas, or your own function; compatible(previous, next) and protoarray check hold a module to the rules in CI (@alexify/protoarray/evolve)

See the guide for every form, and Migrating from 1.x for the upgrade: the wire did not change.

Benchmarks

pnpm bench:compare runs seven payloads through protoarray (its text wire and its binary wire), JSON and eleven JavaScript codecs (protobufjs, protobuf-es, avsc, msgpackr, @msgpack/msgpack, cbor-x, BSON, v8.serialize, superjson, devalue, fast-json-stringify), each library in a fresh process and checked to give the value back before it is timed. Speed is end to end: object to text or bytes and back.

One record: round trip: One record: 10 fields, nested address, tags, 2 orders. Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON.

6 more scenarios 1,000 records: round trip: 1000 records of the same shape. Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON. Sparse record: round trip: 20 optional fields, 3 present. Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON. Typed record: round trip: Enum, boolean and date heavy record. Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON. Long strings: round trip: Long Cyrillic strings. Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON. 100 wrpc packets: round trip: 100 wrpc packets: calls, callbacks and events. Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON. Not run here: fast-json-stringify. 32 doubles: round trip: 32 doubles (where binary formats shine). Encode one payload and decode it, payloads a second, linear scale, higher is better. The line marks JSON.
Payload sizes One record: payload size: One record: 10 fields, nested address, tags, 2 orders. The payload as sent, before compression, linear scale, lower is better. The line marks JSON. 1,000 records: payload size: 1000 records of the same shape. The payload as sent, before compression, linear scale, lower is better. The line marks JSON. Sparse record: payload size: 20 optional fields, 3 present. The payload as sent, before compression, linear scale, lower is better. The line marks JSON. Typed record: payload size: Enum, boolean and date heavy record. The payload as sent, before compression, linear scale, lower is better. The line marks JSON. Long strings: payload size: Long Cyrillic strings. The payload as sent, before compression, linear scale, lower is better. The line marks JSON. 100 wrpc packets: payload size: 100 wrpc packets: calls, callbacks and events. The payload as sent, before compression, linear scale, lower is better. The line marks JSON. Not run here: fast-json-stringify. 32 doubles: payload size: 32 doubles (where binary formats shine). The payload as sent, before compression, linear scale, lower is better. The line marks JSON.
Library One record
round trips/s
1,000 records
round trips/s
Sparse record
round trips/s
Typed record
round trips/s
Long strings
round trips/s
100 wrpc packets
round trips/s
32 doubles
round trips/s
JSON
stringify Β· parse Β· Node 24.14.1
572,000 554 2,730,000 640,000 719,000 16,400 194,000
protoarray
codegen Β· v2.1.0
938,000 849 3,740,000 2,400,000 883,000 29,000 262,000
protoarray
closures, no eval Β· v2.1.0
626,000 574 1,850,000 1,670,000 818,000 16,800 220,000
protoarray
standalone module, no eval Β· v2.1.0
892,000 865 3,520,000 2,360,000 867,000 28,000 273,000
protoarray
codegen + metaschema check Β· v2.1.0
685,000 626 1,700,000 1,780,000 817,000 20,100 235,000
protoarray
columns layout Β· v2.1.0
β€” 869 β€” β€” β€” β€” β€”
protoarray
columns, delta + intern Β· v2.1.0
β€” 967 β€” β€” β€” β€” β€”
protoarray
binary wire Β· v2.1.0
1,130,000 1,330 6,290,000 3,790,000 1,020,000 41,700 1,650,000
protobufjs
reflection, Root.fromJSON Β· v8.8.1
717,000 675 3,530,000 2,920,000 270,000 14,600 520,000
protobuf-es
create Β· toBinary Β· fromBinary Β· v2.16.0
261,000 301 720,000 889,000 209,000 6,400 163,000
avsc
Type.forSchema Β· v5.7.9
840,000 786 1,040,000 1,680,000 300,000 14,300 1,190,000
msgpackr
pack Β· unpack Β· v2.1.0
442,000 455 3,210,000 1,390,000 170,000 13,400 309,000
msgpackr
records, shared structures Β· v2.1.0
916,000 1,000 4,000,000 2,040,000 188,000 21,500 1,010,000
@msgpack/msgpack
Encoder Β· Decoder Β· v3.1.3
356,000 370 2,060,000 660,000 244,000 11,400 149,000
cbor-x
records Β· v1.6.6
358,000 865 1,650,000 820,000 180,000 16,200 269,000
BSON
serialize Β· deserialize Β· v7.3.3
273,000 270 1,820,000 780,000 236,000 8,000 281,000
v8.serialize
structured clone, Node only Β· Node 24.14.1
277,000 432 650,000 533,000 458,000 12,500 309,000
superjson
stringify Β· parse Β· v2.2.6
109,000 94 639,000 213,000 246,000 3,620 73,500
devalue
stringify Β· parse Β· v6.0.3
184,000 171 1,010,000 383,000 344,000 5,960 123,000
fast-json-stringify
compiled stringify, JSON.parse Β· v7.1.0
615,000 577 3,560,000 1,820,000 664,000 β€” 395,000

Apple M3 Max Β· 16 cores Β· Node v24.14.1 Β· median of 5 Γ— 400 ms rounds, each library and scenario in a fresh process Β· 2026-10-21. A round trip is one encode and one decode of the payload; medians, rounded to three significant figures; β€” where the scenario does not apply.

The interactive charts, encode, decode and the payload sizes of every scenario, and how to read them are on the benchmarks page.

Cold start

Load the library, build the schema of one record, encode it and decode it once, in a fresh process. A client that builds its schemas from snapshots loads protoarray alone, and a standalone module has its codec written already.

Cold start: Load the library, build the schema of one record, encode it and decode it once: a fresh process, median of 10 runs. Lower is better.

Library Load Build First encode First decode Total
JSON
stringify Β· parse Β· Node 24.14.1
0.00 ms 0.02 ms 0.02 ms 0.01 ms 0.06 ms
v8.serialize
structured clone, Node only Β· Node 24.14.1
0.27 ms 0.01 ms 0.04 ms 0.04 ms 0.36 ms
protoarray
standalone module, no eval Β· v2.1.0
1.2 ms 0.01 ms 0.15 ms 0.15 ms 1.5 ms
@msgpack/msgpack
Encoder Β· Decoder Β· v3.1.3
2.9 ms 0.10 ms 0.37 ms 0.57 ms 3.9 ms
devalue
stringify Β· parse Β· v6.0.3
5.1 ms 0.01 ms 0.49 ms 0.26 ms 5.9 ms
msgpackr
records, shared structures Β· v2.1.0
5.3 ms 0.03 ms 0.63 ms 0.43 ms 6.4 ms
superjson
stringify Β· parse Β· v2.2.6
5.8 ms 0.01 ms 0.58 ms 0.04 ms 6.4 ms
cbor-x
records Β· v1.6.6
5.3 ms 0.03 ms 0.64 ms 0.47 ms 6.5 ms
BSON
serialize Β· deserialize Β· v7.3.3
5.4 ms 0.02 ms 0.51 ms 0.60 ms 6.5 ms
protoarray
codegen, from a snapshot Β· v2.1.0
4.4 ms 2.8 ms 0.13 ms 0.15 ms 7.6 ms
avsc
Type.forSchema Β· v5.7.9
8.2 ms 0.59 ms 0.18 ms 0.17 ms 9.1 ms
protobufjs
reflection, Root.fromJSON Β· v8.8.1
11.1 ms 1.5 ms 3.1 ms 0.35 ms 16.2 ms
protoarray
codegen Β· v2.1.0
11.9 ms 4.3 ms 0.13 ms 0.15 ms 16.5 ms
protoarray
closures, no eval Β· v2.1.0
12.5 ms 3.9 ms 0.12 ms 0.17 ms 16.7 ms
protoarray
binary wire Β· v2.1.0
13.6 ms 4.2 ms 1.6 ms 0.28 ms 19.8 ms
fast-json-stringify
compiled stringify, JSON.parse Β· v7.1.0
21.0 ms 4.5 ms 0.26 ms 0.01 ms 25.7 ms
protobuf-es
create Β· toBinary Β· fromBinary Β· v2.16.0
24.5 ms 0.38 ms 0.80 ms 0.06 ms 25.9 ms

Bundle size

A browser bundle that builds the schema of one record and round-trips it (esbuild, minified, tree-shaken; pnpm size:compare).

Bundle size: A browser bundle that builds the schema of one record, encodes it and decodes it (esbuild, minified, tree-shaken), min+gzip. Lower is better.

Library min min+gzip
devalue
stringify Β· parse Β· v6.0.3
10.7 KB 4.0 KB
superjson
stringify Β· parse Β· v2.2.6
11.1 KB 4.0 KB
@msgpack/msgpack
Encoder Β· Decoder Β· v3.1.3
20.7 KB 5.8 KB
protoarray
standalone module, no eval Β· v2.1.0
19.0 KB 7.2 KB the module protoarray compile writes for this schema, with its inline runtime
msgpackr
records, shared structures Β· v2.1.0
27.4 KB 10.3 KB
cbor-x
records Β· v1.6.6
28.6 KB 10.6 KB
protobuf-es
create Β· toBinary Β· fromBinary Β· v2.16.0
66.6 KB 18.2 KB the runtime and the descriptor generated code embeds
BSON
serialize Β· deserialize Β· v7.3.3
74.8 KB 22.2 KB
protoarray
codegen, from a snapshot Β· v2.1.0
63.2 KB 23.0 KB the schema built from its snapshot: a client without metaschema
protobufjs
protobufjs/light, Root.fromJSON Β· v8.8.1
105.7 KB 29.2 KB reflection; static code from pbjs with protobufjs/minimal is smaller, plus the generated code
protoarray
binary wire, from a snapshot Β· v2.1.0
90.7 KB 32.4 KB the core and the ./binary subpath, the schema built from its snapshot
avsc
Type.forSchema Β· v5.7.9
115.4 KB 34.3 KB avsc's browser build of types, with the buffer and util polyfills it requires
protoarray
codegen, metaschema definition Β· v2.1.0
122.3 KB 43.6 KB protoarray and @alexify/metaschema, which reads the definition and validates values
fast-json-stringify
compiled stringify, JSON.parse Β· v7.1.0
217.4 KB 62.9 KB encode only; JSON.parse decodes

Release over release

Every release on the scenarios of the comparison, on both backends and, from 2.1, the binary wire, with today's harness (pnpm bench:releases).

protoarray 2.1.0 against its first releases: How many times as fast, scenario by scenario, against the oldest release that has it (1 = as fast; unions came in 1.1). Higher is better.

Every release, scenario by scenario One record, release over release: One record: 10 fields, nested address, tags, 2 orders. stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. 1,000 records, release over release: 1000 records of the same shape. stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. Sparse record, release over release: 20 optional fields, 3 present. stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. Typed record, release over release: Enum, boolean and date heavy record. stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. Long strings, release over release: Long Cyrillic strings. stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. 100 wrpc packets, release over release: 100 wrpc packets: calls, callbacks and events. stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. 32 doubles, release over release: 32 doubles (where binary formats shine). stringify and parse (toBinary and fromBinary on the binary wire), ops/s, linear scale, higher is better. Schema.from of one record, release over release: Building the schema from its definition (2.x: metaschema reads it first), schemas/s, linear scale, higher is better.
Release One record
encode / decode
1,000 records
encode / decode
Sparse record
encode / decode
Typed record
encode / decode
Long strings
encode / decode
100 wrpc packets
encode / decode
32 doubles
encode / decode
Schema.from
schemas/s
1.0.0
codegen
2,030,000 / 1,650,000 1,960 / 1,500 7,200,000 / 7,340,000 3,850,000 / 4,770,000 1,790,000 / 1,660,000 β€” 403,000 / 734,000 36,200
1.1.0
codegen
2,030,000 / 1,640,000 1,970 / 1,500 7,210,000 / 7,180,000 4,880,000 / 4,790,000 1,750,000 / 1,670,000 66,800 / 47,400 409,000 / 731,000 31,600
2.0.0
codegen
2,040,000 / 1,690,000 1,970 / 1,480 7,020,000 / 7,460,000 4,670,000 / 4,780,000 1,800,000 / 1,700,000 67,600 / 50,600 427,000 / 731,000 14,600
2.1.0
codegen
2,070,000 / 1,740,000 2,000 / 1,510 7,360,000 / 7,560,000 4,700,000 / 4,860,000 1,820,000 / 1,700,000 68,400 / 50,600 421,000 / 739,000 14,900
1.0.0
closures
1,550,000 / 971,000 1,420 / 937 3,180,000 / 2,590,000 2,950,000 / 2,850,000 1,670,000 / 1,540,000 β€” 360,000 / 573,000 172,000
1.1.0
closures
1,470,000 / 1,040,000 1,380 / 976 2,930,000 / 2,460,000 3,540,000 / 3,110,000 1,680,000 / 1,550,000 47,800 / 26,000 364,000 / 568,000 98,400
2.0.0
closures
1,470,000 / 1,060,000 1,390 / 978 2,950,000 / 2,360,000 3,450,000 / 3,090,000 1,690,000 / 1,560,000 48,100 / 26,800 359,000 / 567,000 24,000
2.1.0
closures
1,570,000 / 1,080,000 1,440 / 968 3,270,000 / 4,280,000 3,520,000 / 3,100,000 1,730,000 / 1,590,000 49,000 / 26,300 370,000 / 571,000 23,800
2.1.0
binary wire
2,120,000 / 2,260,000 3,060 / 2,270 11,900,000 / 12,700,000 6,490,000 / 8,780,000 1,850,000 / 2,240,000 111,000 / 69,700 2,480,000 / 4,970,000 β€”

Apple M3 Max Β· 16 cores Β· Node v24.14.1 Β· median of 5 Γ— 400 ms rounds, each release, backend and scenario in a fresh process Β· 2026-10-21. ops/s of stringify / parse (toBinary / fromBinary on the binary wire), medians; β€” where the release has no union, and for the binary wire, which builds the schema the text wire does.

On its two backends

pnpm bench: every shape through both backends on the text wire and on the binary one, against JSON.stringify and JSON.parse of the keyed object, and the layouts of a list.

protoarray on its two backends, on both wires, and its layouts protoarray on its two backends: encode: pnpm bench: stringify against JSON.stringify of the keyed object, ops/s, log scale, higher is better. protoarray on its two backends: decode: pnpm bench: parse against JSON.parse of the keyed object, ops/s, log scale, higher is better. protoarray's binary wire on its two backends: encode: pnpm bench: toBinary against JSON.stringify of the keyed object, ops/s, log scale, higher is better. protoarray's binary wire on its two backends: decode: pnpm bench: fromBinary against JSON.parse of the keyed object, ops/s, log scale, higher is better. Layouts of a list of a thousand records: pnpm bench, generated code: lists a second, linear scale, higher is better.

Compared with alternatives

protoarray next to twenty-two JavaScript serialization libraries: typed and compact JSON, the interface definition languages (Protocol Buffers, Avro, Thrift, FlatBuffers, Cap'n Proto, Bebop), and the schemaless binary formats. Every cell was checked against the source of the published package; the page has the method and what the table shows.

Feature protoarray
positional JSON
JSON
built in
fast-json-stringify
JSON Schema serializer
superjson
typed JSON
devalue
typed JSON
JSONH
packed JSON
compress-json
compressed JSON
protobufjs
IDL Β· Protobuf
protobuf-es
IDL Β· Protobuf
ts-proto
Protobuf codegen
avsc
IDL Β· Avro
Apache Thrift
IDL Β· Thrift
FlatBuffers
IDL Β· zero-copy
Cap'n Proto
IDL Β· zero-copy
Bebop
IDL Β· binary
typia
AOT compiler
schemapack
schema Β· binary
msgpackr
MessagePack
@msgpack/msgpack
MessagePack
cbor-x
CBOR
BSON
BSON
v8.serialize
built in Β· Node
Amazon Ion
self-describing
Package
Version compared2.0.0Node 24.14.1 (V8 13.6)7.1.02.2.66.0.30.0.63.4.08.8.1 (protobufjs-cli 2.7.1)2.16.0 (protoc-gen-es 2.16.0)2.15.35.7.90.25.025.9.230.0.1613.2.315.1.01.4.22.1.03.1.31.6.67.3.3Node 24.14.1 (format version 15)5.2.1
Latest release2026-10 (this release)Node 24 (built in)2026-102025-112026-102017-072025-092026-102026-092026-102025-072026-092025-092026-082025-092026-102016-082026-082025-122026-082026-09Node 24 (built in)2024-04
Runtime dependenciesβœ… 0βœ… 0 (built in)6: ajv, ajv-formats, fast-uri, rfdc, …1: copy-anythingβœ… 0βœ… 0βœ… 01: longβœ… 01 at runtime: @bufbuild/protobufβœ… 05: ws, uuid, node-int64, isomorphic-ws, browser-or-nodeβœ… 0βœ… 0 (an optional peer for the compiler)βœ… 03: @typia/interface, @typia/utils, randexp + optional peer ttscβœ… 01 optional: msgpackr-extract (native)βœ… 01 optional: cbor-extract (native)βœ… 0βœ… 0 (built in)βœ… 0
LicenseMITbuilt in (ECMA-262)MITMITMITMIT (source header)BSD-2-ClauseBSD-3-ClauseApache-2.0 AND BSD-3-ClauseISCMITApache-2.0Apache-2.0MITApache-2.0MITMITMITISCMITApache-2.0MIT (Node.js)Apache-2.0
Runtime
Runs onβœ… Node 18+, browsers, edgeβœ… every JS runtimeβœ… Node, browsers (bundled with Ajv)βœ… Node β‰₯ 16, browsersβœ… Node 20+, browsers, Deno, Bun, edgeβœ… Node, browsers (ES3), Deno, Bun, edgeβœ… Node, browsers (IIFE bundle), Deno, Bunβœ… Node β‰₯ 12, browsers, Deno, Bunβœ… Node, browsers, Deno, Bun, edgeβœ… anywhere @bufbuild/protobuf/wire runs⚠️ Node β‰₯ 0.11; browsers need buffer/util/stream shims2⚠️ Node β‰₯ 10.18; browsers need util/Buffer shims3βœ… Node, browsers, Deno, Bun (no engines)βœ… Node, browsers, Deno, Bun (no engines)βœ… Node, browsers, Deno, Bun (no engines)βœ… Node, browsers (build needs ttsc, Node β‰₯ 22.15)⚠️ Node; browsers need the buffer shim4βœ… Node, browsers, Deno, Bunβœ… Node β‰₯ 18, browsers, Deno, Bunβœ… Node, browsers, Denoβœ… Node β‰₯ 20.19, browsers, React Native❌ Node only (node:v8)βœ… Node, browsers (no engines, needs BigInt)
Module formatCJS, ESM named importsbuilt in (global JSON)CJSESM onlyESMCJS + global script (JSONH)CJS (+ IIFE browser bundle)CJS + UMD bundle (dist/protobuf.js)ESM + CJSgenerated .ts (ESM imports); plugin CJSCJSCJS (generated code CJS or ESM)CJS + ESM (module field)ESMESM + CJSESM + CJSCJSESM + CJS (+ UMD)ESM + CJS + UMDESM + CJS (+ UMD)ESM + CJSbuilt in (node:v8)CJS + ESM (bundlers) + AMD
Works without eval (strict CSP, edge)βœ… closures fallback; standalone modules at codegen speedβœ… native, no code generation⚠️ build uses new Function; standalone modules don't5βœ… no eval or Function in dist/βœ… stringify/parse never evalβœ… neverβœ… never⚠️ reflection needs new Function; static code doesn't6βœ… never: closures over descriptorsβœ… never: plain generated code❌ new Function per type; required7βœ… never (library and generated code)βœ… never (runtime and flatc output)βœ… neverβœ… never (runtime and bebopc output)βœ… never (code emitted at build time)❌ new Function per build()8⚠️ new Function for records; catches failure9βœ… never⚠️ probes new Function at import; compiles records10βœ… never (Code values are not evaluated)βœ… native, no code generationβœ… never
Schema at runtime, no code generatorβœ… a definition or a snapshot at runtimeβœ… no schema neededβœ… JSON Schema object at runtimeβœ… no schema neededβœ… no schema neededβœ… no schema neededβœ… no schema neededβœ… protobuf.parse/load or Root.fromJSON at runtime⚠️ createFileRegistry from descriptors; no .proto parser❌ needs protoc/buf + pluginβœ… Type.forSchema(json) at runtime❌ needs thrift --gen js:node❌ requires flatc code generation❌ requires C++ capnpc + capnp-es plugin⚠️ BinarySchema reader/writer; schema from bebopc❌ every function throws without the ttsc transformβœ… build(schema) at runtimeβœ… no schema neededβœ… no schema neededβœ… no schema neededβœ… no schema neededβœ… no schema neededβœ… no schema needed
TypeScript typesβœ… bundled d.ts, types from the metaschema schemaβœ… TypeScript's lib.es5.d.tsβœ… bundled d.tsβœ… bundled d.tsβœ… bundled d.ts❌ none (no @types/jsonh)βœ… bundled d.tsβœ… bundled d.ts; pbts for messagesβœ… bundled d.ts; generated message typesβœ… generated from the IDLβœ… bundled d.ts (types/index.d.ts)βœ… generated from the IDL (js:ts)βœ… generated from the IDL (flatc --ts)βœ… generated from the IDL (-ots, -odts)βœ… generated from the IDL (bebopc)βœ… bundled d.ts (the types are the schema)❌ none (no @types/schemapack)βœ… bundled d.tsβœ… bundled d.ts (written in TypeScript)βœ… bundled d.tsβœ… bundled d.ts⚠️ @types/nodeβœ… bundled d.ts
Schema
How the shape is describedmetaschema definition (a JS object) or its JSON snapshotnone (schemaless)JSON Schema (draft 7, plus nullable)none (schemaless)none (schemaless); reducers/revivers for custom typesnone; keys from the first recordnone (schemaless).proto file, JSON descriptor or reflection API.proto file, compiled to descriptors.proto file (via protoc)Avro schema JSON (or .avdl IDL).thrift IDL.fbs IDL (flatc also reads .proto).capnp IDL.bop IDLTypeScript type (+ tags.*)JS object of type names ({ age: 'uint8' })none (records inferred from object keys)none (schemaless)none (records inferred from object keys)none (schemaless)none (schemaless)none (self-describing)
The schema is data (store it, send it)βœ… the snapshot is JSON a server hands to clientsβ€” schemalessβœ… JSON Schema is plain data11β€” schemalessβ€” schemalessβ€” schemalessβ€” schemalessβœ… JSON descriptors: root.toJSON() / Root.fromJSONβœ… FileDescriptorSet β†’ createFileRegistry⚠️ export only (outputSchema=true)βœ… Avro schema JSON; type.schema() exports it❌ generated code only⚠️ export only: .bfbs, JSON Schema via flatc⚠️ schema nodes as capnp messages; no dynamic codecβœ… binary schema + BinarySchema (RecordReader)⚠️ export only: json.schema<T>(), protobuf.message<T>()⚠️ plain JSON, but keys become codeβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemalessβ€” schemaless
Static type from the schemaβœ… from the metaschema schema (Standard Schema types)β€” schemaless⚠️ third-party: TypeBox, json-schema-to-tsβ€” schemalessβ€” schemaless; parse returns anyβ€” schemalessβ€” schemaless; decompress<T> is a castβœ… generated by pbts from static codeβœ… generated by protoc-gen-esβœ… generated interfaces⚠️ third-party @ovotech/avro-ts codegenβœ… generated d.ts (js:ts)βœ… generated by flatc --tsβœ… generated by capnp-es -otsβœ… generated by bebopcβœ… the TS type is the schema❌ noneβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemalessβ€” schemaless
Values checked before encodeβœ… metaschema check of the same definition12β€” schemaless❌ coerces: '12' β†’ 12, null β†’ 013β€” schemalessβ€” schemalessβ€” schemalessβ€” schemalessβœ… Type.verify; encode coerces silently⚠️ toBinary range-checks numbers; strings coerced14⚠️ writer range-checks numbers; no verify15βœ… type.isValid(v, { errorHook }); toBuffer throws❌ string in i32 β†’ 0, 2^40 β†’ 016❌ coerces: string in int β†’ 0, overflow wraps17❌ coerces: string in UInt32 β†’ 018❌ trusts: string in uint32 β†’ 019βœ… assertEncode, assertStringify, typia.assert⚠️ typeof and range checks, on by default20β€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemalessβ€” schemaless
Wire format
Wire formatJSON text (positional arrays); bytes through ./binaryJSON textJSON textJSON text + metadata ({ json, meta })JSON text (flat value table, index refs)21JSON text ([n, …keys, …values])JSON array ([values[], rootKey], encoded strings)binary (Protobuf); ProtoJSON via ext/protojsonbinary (Protobuf); ProtoJSON toJson/fromJsonbinary (Protobuf); JSON via toJSON/fromJSONbinary (Avro); Avro JSON via toStringbinary (Binary/Compact) or JSON (TJSONProtocol)binary (FlatBuffers: vtables + offsets)binary (Cap'n Proto, optional packing)binary (Bebop)JSON text (json.*) or binary Protobuf (protobuf.*)binary (custom, big-endian, varints)binary (MessagePack + record extension)binary (MessagePack)binary (CBOR + record tags)binary (BSON)binary (V8 structured clone)Ion text or Ion binary
Human-readable payloadβœ… JSON arraysβœ… JSONβœ… JSONβœ… JSONβœ… JSON, values referenced by index22βœ… JSON⚠️ JSON, but base62 numbers and refs❌ binary❌ binary❌ binary⚠️ binary; Avro JSON via type.toString()⚠️ binary; TJSONProtocol is JSON❌ binary❌ binary❌ binaryβœ… JSON path; protobuf path is binary❌ binary❌ binary❌ binary❌ binary❌ binary (EJSON for text)❌ binary⚠️ Ion text (JSON superset, not JSON)
Travels over a JSON or text channel as isβœ… plain JSONβœ… it is JSONβœ… it is JSONβœ… it is JSONβœ… JSON stringβœ… JSON stringβœ… JSON.stringify(compress(x))❌ binary; base64 to travel as text❌ binary; base64 to travel as text❌ binary; base64 to travel as textβœ… only as Avro JSON (toString/fromString)βœ… only with TJSONProtocol❌ needs base64❌ needs base64❌ needs base64βœ… JSON path; protobuf needs base64❌ Buffer; base64 needed❌ binary, needs base64❌ binary, needs base64❌ binary, needs base64❌ binary; EJSON is a separate JSON form❌ needs base64βœ… Ion text is a string
Field names on the wire❌ positionalβœ… every messageβœ… every messageβœ… every message (and in meta paths)βœ… every object⚠️ once per collection (header)⚠️ once per distinct key set❌ tag numbers❌ tag numbers❌ tag numbers❌ positional (schema order)❌ tag numbers (field ids)❌ vtable slots (field ids)❌ fixed offsets❌ positional structs; 1-byte tags in messagesβœ… JSON: every message; protobuf: tag numbers❌ positional (sorted key order)⚠️ once per structure (new Packr() records)βœ… every object⚠️ once per structure (new Encoder() records)βœ… every documentβœ… every object, repeatedβœ… text: each time; binary: once (symbol table)
Decodable without the schema⚠️ parses as JSON; keys need the schemaβœ… plain JSONβœ… plain JSONβœ… meta carries the typesβœ… type tags in the payloadβœ… keys in the headerβœ… keys and value kinds encoded⚠️ to raw tags only (Reader)⚠️ to raw tags only (BinaryReader)⚠️ to raw tags only (BinaryReader)❌ needs the writer schema⚠️ to raw tags only (type ids travel)❌ needs the schema⚠️ to raw structs and lists only❌ needs the schemaβœ… JSON path; protobuf to raw tags only❌ schema requiredβœ… yes, unless shared structures are usedβœ… yesβœ… yes, unless shared structures are usedβœ… yes (type-tagged elements)βœ… tagged valuesβœ… typed values, names in stream
Lazy or zero-copy reads❌ none❌ whole text parsed❌ decode is JSON.parse❌ whole text parsed❌ whole payload revived❌ none❌ none❌ full decode; bytes are views❌ full decode; bytes are views❌ full decode❌ whole record decoded❌ whole struct decodedβœ… accessors read the buffer in placeβœ… lazy accessors❌ decodes the whole record (bytes are views)❌ none❌ none⚠️ only via structon (split out in 2.0)❌ none❌ none⚠️ experimental onDemand.parseToElements❌ whole buffer decodedβœ… binary Reader skips values by length
Other languages read the wire⚠️ the wire format is specified; JS onlyβœ… every language (RFC 8259)βœ… plain JSON: any language⚠️ a community Elixir port❌ JS only23βœ… Python 2, PHP 5, Ruby (in package)βœ… Python, PHP, Rust ports24βœ… C++, Java, Go, Python, C#, Rustβ€¦βœ… C++, Java, Go, Python, C#, Rustβ€¦βœ… C++, Java, Go, Python, C#, Rustβ€¦βœ… Apache Avro: Java, Python, C, C#, Rust, Goβœ… C++, Java, Python, Go, Rust, C#, β€¦βœ… C++, Java, Go, Rust, Python, C#, Swiftβœ… C++, Rust, Go, Java, Python, C#βœ… C#, Rust, C++, C, Dart, Pythonβœ… JSON anywhere; protobuf wire via .proto export❌ JS only⚠️ any MessagePack library; records are msgpackr-onlyβœ… MessagePack in C, Go, Python, Rust, Javaβ€¦βš οΈ any CBOR library; records are cbor-x-onlyβœ… every MongoDB driver (C, Java, Go, Python…)❌ V8 onlyβœ… Java, C, Python, Rust, Go, .NET
Types
64-bit integers, BigIntβœ… bigint as a decimal string⚠️ doubles; BigInt throws TypeError⚠️ BigInt β†’ JSON number; JSON.parse rounds itβœ… BigInt, as a decimal stringβœ… BigInt (["BigInt","…"])❌ BigInt throws (JSON.stringify)❌ BigInt throws unsupported data type⚠️ Long objects; BigInt only via toObjectβœ… BigInt (jstype = JS_STRING β†’ string)⚠️ number by default; forceLong=bigint⚠️ numbers; throws past 2^53⚠️ Int64 objects; BigInt with js:node,bigintβœ… BigInt (readInt64 returns bigint)βœ… BigIntβœ… BigInt⚠️ bigint ↔ int64 in protobuf; JSON rejects bigint❌ max int32/uint32; varint Β±2Β³β°βœ… BigInt in and out (Β±2⁢⁴)⚠️ numbers by default; useBigInt64 opt-inβœ… BigInt; bignum tags past 64 bits⚠️ Long/number by default; useBigInt64 opt-inβœ… BigIntβœ… arbitrary-size ints, bigIntValue()
Datesβœ… Date ↔ epoch ms❌ Date β†’ ISO string, comes back a string⚠️ format: 'date-time' β†’ ISO string, string backβœ… Date in, Date outβœ… Date in, Date out (ISO)❌ ISO string out (via JSON)❌ Date silently becomes {}⚠️ Timestamp { seconds, nanos }, no Date⚠️ Timestamp + timestampFromDate/timestampDateβœ… Date via Timestamp (useDate, default)⚠️ logical type you implement (logicalTypes)❌ none❌ no date type; int64 by hand❌ noneβœ… date ↔ Date (100 ns ticks)⚠️ Date β†’ ISO string; parse returns a string❌ noneβœ… Date via the timestamp extension (βˆ’1)βœ… Date via the timestamp extension (βˆ’1)βœ… Date via tag 1 (epoch)βœ… Date via UTC datetimeβœ… Date in, Date outβœ… Date in, dom.Timestamp (a Date) out
Binary data (Uint8Array)βœ… Uint8Array ↔ canonical base64❌ Uint8Array β†’ {"0":1,"1":2}❌ Uint8Array in a string β†’ "1,2"βœ… typed arrays, as a number arrayβœ… typed arrays, ArrayBuffer (base64 inside)25❌ Uint8Array β†’ {"0":1,…}❌ Uint8Array β†’ {"0":1,…}βœ… Uint8Array (Buffer on Node)βœ… Uint8Arrayβœ… Uint8Arrayβœ… bytes/fixed as Bufferβœ… binary β†’ Bufferβœ… [ubyte] β†’ xArray() Uint8Array viewβœ… Data (toUint8Array() live view)βœ… byte[] ↔ Uint8Array (a view)⚠️ Uint8Array in protobuf only⚠️ buffer type, Node Buffer onlyβœ… bin β†’ Buffer/Uint8Arrayβœ… bin β†’ Uint8Arrayβœ… byte string β†’ Uint8Array/Buffer⚠️ Binary wrapper; promoteBuffers for bytesβœ… typed arrays, Buffer, ArrayBufferβœ… blob ↔ Uint8Array
Map and Setβœ… { map }, { set }❌ Map and Set β†’ {}❌ Map β†’ {}βœ… bothβœ… both❌ become {}❌ silently become {}⚠️ maps as plain objects; no Set⚠️ maps as plain objects; no Set⚠️ Map with useMapType=true; no Set⚠️ map β†’ plain object; no Set⚠️ map β†’ object, set β†’ array❌ neither; vectors only❌ neither⚠️ map[K, V] ↔ Map; no Set⚠️ Map in protobuf only; no Set❌ noneβœ… both with moreTypes or structuredClone❌ Map and Set encode as {}βœ… Map (tag 259) and Set (tag 258)⚠️ Map in, plain object out; no Setβœ… both❌ Map and Set dump as {}
Enumsβœ… as an indexβ€” no schema⚠️ enum not enforced; the string travelsβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβœ… number on the wire; names via enums: Stringβœ… number on the wire; name in JSONβœ… number on the wire; stringEnums in TSβœ… as an index; JS sees the symbolβœ… as the i32 numberβœ… the integer value travelsβœ… the uint16 ordinal travelsβœ… the integer value travelsβœ… literal unions; the string travels❌ noneβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schema⚠️ symbols (no enum type)
Tagged unionsβœ… discriminated union: [tag, …fields]β€” any value⚠️ untagged anyOf/oneOf; Ajv picks the branchβ€” any valueβ€” any valueβ€” any valueβ€” any valueβœ… oneof (virtual property names the case)βœ… oneof as { case, value }βœ… oneof=unions gives a $case unionβœ… Avro unions (branch index on wire)βœ… union (one field id set)βœ… union (type byte + offset)βœ… union + which()βœ… union β†’ { tag, value }βœ… TS union types (protobuf oneof)❌ noneβ€” any valueβ€” any valueβ€” any valueβ€” any valueβ€” any valueβ€” any value; annotations can tag it
Optional fields: absent and null⚠️ optional fields; absent and null both null26βœ… absent vs null; undefined dropped⚠️ absent omitted; null β†’ ""/0 unless nullableβœ… absent, undefined and null kept apartβœ… absent, undefined, null all distinct⚠️ absent becomes null; extra keys droppedβœ… absent and null distinctβœ… presence with proto3 optional/proto2βœ… presence with optional; isFieldSetβœ… optional β†’ field?: T | undefined⚠️ ["null", T] union; absent and null mergeβœ… optional; absent reads as nullβœ… = null scalars; tables, strings nullable⚠️ scalars: default = absent; pointers _hasX()βœ… message fields: absent β‰  setβœ… ?: absent vs null distinct❌ every field requiredβœ… absent, null and undefined distinctβœ… absent vs null; undefined β†’ nullβœ… absent, null and undefined distinctβœ… absent vs null; undefined droppedβœ… absent, undefined and null kept apartβœ… absent vs null / typed null.int
Defaults on decodeβœ… the definition's defaultβ€” no schema⚠️ default written on encode, not on decodeβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schema⚠️ zero values; [default] in proto2/editions only⚠️ zero values; [default] in proto2/editions only⚠️ zero values; proto2 [default] honouredβœ… field default (encode and resolver)βœ… IDL defaults set in the constructorβœ… schema defaults returned when absentβœ… schema defaults (XOR-encoded)❌ none (absent β†’ undefined)⚠️ protobuf zero values; Default<V> is schema-only❌ noneβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemaless
Recursive types, cyclic valuesβœ… references by name, recursionβœ… any nesting; cyclic values throw TypeErrorβœ… $ref recursion; cyclic values overflowβœ… any nesting; cycles and shared refsβœ… any nesting; cyclic and repeated refsβœ… any nesting (JSON); no cyclesβœ… any nesting; no cyclesβœ… recursive messages, references by nameβœ… recursive messages, references by nameβœ… recursive messages, references by nameβœ… named types by reference; no cyclic valuesβœ… structs reference structs; no cyclic valuesβœ… tables may contain themselvesβœ… structs may contain themselvesβœ… messages may contain themselvesβœ… recursive types; cyclic values rejected❌ none (self-referencing schema overflows build)βœ… any nesting; cycles with structuredCloneβœ… any nesting on decode; no cyclesβœ… any nesting; cycles with structuredCloneβœ… any nesting; cycles rejectedβœ… any nesting; cycles and shared refsβœ… any nesting; no cyclic values
Fixed-point decimalsβœ… decimals: n β†’ a scaled integer❌ doubles only❌ none (multipleOf ignored)❌ none (registerCustom recipe)❌ doubles only❌ none❌ none❌ none (int64 minor units by hand)❌ none (int64 minor units by hand)❌ none (int64 minor units by hand)⚠️ decimal logical type, not implemented❌ none❌ none❌ none❌ none⚠️ MultipleOf<0.01> check; travels as a number❌ none❌ none (float32 rounding options only)❌ none⚠️ tag 4 decoded to a float; not encodedβœ… Decimal128❌ noneβœ… decimal type (ion.Decimal)
Bit flags, packed scalarsβœ… flags β†’ a bitmask❌ none❌ none❌ none❌ none❌ none❌ noneβœ… packed repeated scalars (proto3 default)βœ… packed repeated scalars (proto3 default)βœ… packed repeated scalars (proto3 default)❌ none (a byte per boolean)❌ noneβœ… (bit_flags) enumsβœ… Bool fields and List(Bool) bit-packedβœ… @flags enums⚠️ packed repeated scalars (protobuf path)❌ a byte per boolean⚠️ typed arrays only with moreTypes⚠️ typed arrays as raw bin, type lostβœ… typed arrays (RFC 8746 tags)βœ… Binary vectors: int8, float32, packed bits❌ none❌ none
A free-form value in a typed schemaβœ… json, any fieldsβ€” schemalessβœ… {} schema or additionalProperties: trueβ€” schemalessβ€” schemalessβ€” schemalessβ€” schemaless⚠️ Struct as raw fields/Value messagesβœ… google.protobuf.Struct as JsonObjectβœ… google.protobuf.Struct as a plain object❌ none (no any type)❌ none⚠️ FlexBuffers bytes in a [ubyte] field⚠️ AnyPointer, cast by hand❌ none⚠️ unknown/any in JSON only (protobuf bans any)❌ noneβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemalessβ€” schemaless
Schema evolution
Add fieldsβœ… append at the end; trailing nulls trimmedβœ… no schemaβœ… keyed JSON: add propertiesβœ… no schemaβœ… no schema; keys travelβœ… no schema; keys in the headerβœ… no schema; keys travelβœ… new tag numbersβœ… new tag numbersβœ… new tag numbersβœ… with default, via createResolverβœ… new field ids; old readers skip themβœ… append fields or explicit (id: n)βœ… new @n ordinals; old data reads defaultsβœ… messages: new field indicesβœ… JSON: names travel; protobuf: new Sequence<N>❌ new key shifts sorted positionsβœ… no schema; keys travelβœ… no schema; keys travelβœ… no schema; keys travelβœ… no schema; keys travelβœ… no schemaβœ… no schema
Rename a fieldβœ… positions travel; aliases in resolve❌ names travel❌ names travel❌ names travel❌ names travel❌ names travel❌ names travelβœ… tag numbers travel, not namesβœ… tag numbers travel, not namesβœ… tag numbers travel, not namesβœ… aliases in resolution; names not on wireβœ… field ids travel, names do notβœ… slots travel, names do notβœ… names do not travelβœ… indices and positions travel⚠️ protobuf with Sequence<N>; JSON names travel⚠️ positions travel, but sorted by key❌ names travel❌ names travel❌ names travel (keyMap maps names to numbers)❌ names travel❌ names travel❌ names travel
Reader and writer schema resolutionβœ… resolve(writer, reader) (./resolve)β€” schemaless❌ none: decode is JSON.parseβ€” schemalessβ€” schemalessβ€” schemalessβ€” schemaless⚠️ tag matching only; no resolver step⚠️ tag matching only; no resolver step⚠️ tag matching only; no resolver stepβœ… reader.createResolver(writer): aliases, defaults, promotions⚠️ by field id; a mismatched type is skipped❌ none; append-only rules instead❌ none; layout rules instead❌ none❌ none❌ noneβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemalessβ€” schemaless
Schema id or fingerprintβœ… schema.id, FNV-1a (32 or 64 bits) of the canonical formβ€” schemaless❌ none ($id is a name)β€” schemalessβ€” schemalessβ€” schemalessβ€” schemaless❌ none❌ none❌ noneβœ… type.fingerprint(): MD5 of canonical form❌ none in JS⚠️ 4-char file_identifier, set by hand⚠️ 64-bit type ids, stable across versions⚠️ @opcode 4-byte id, by hand❌ none❌ none❌ none for shared structuresβ€” no schema❌ none for shared structuresβ€” no schemaβ€” schemalessβ€” schemaless
Registry or envelope with the schema idβœ… SchemaRegistry: [id, payload] (./registry)β€” schemaless❌ noneβ€” schemalessβ€” schemalessβ€” schemalessβ€” schemaless⚠️ Any type URL; Confluent via third party⚠️ Any type URL; Confluent via @confluentinc/schemaregistry⚠️ google.protobuf.Any type URL only⚠️ container files embed schema; registry third-party❌ none (RPC envelope names the method)⚠️ file_identifier in the buffer header❌ segment framing carries no type id❌ none (opcode written by hand)❌ none❌ none (README: add a uint8 field by hand)❌ record ids only; structures kept out of bandβ€” no schema❌ record ids only; structures kept out of bandβ€” no schemaβ€” schemaless⚠️ shared symbol tables by name/version (Catalog)
Unknown fields from a newer writerβœ… trailing extras ignored, or rejected with strictβ€” schemalessβœ… JSON.parse keeps them; encode drops undeclaredβ€” schemalessβ€” schemalessβ€” schemalessβ€” schemalessβœ… dropped by default; discardUnknown = false keepsβœ… kept in $unknown, re-encodedβœ… dropped by default; unknownFields=true keeps⚠️ skipped by the resolver; not kept⚠️ skipped (input.skip), not kept⚠️ ignored; lost on unpack() β†’ pack()βœ… kept in the buffer; copyFrom copies them⚠️ ignored: skips to the message endβœ… ignored; assertEquals rejects, assertPrune strips⚠️ trailing bytes ignored; no strict modeβ€” no schemaβ€” no schemaβ€” no schemaβ€” no schemaβ€” schemalessβ€” schemaless
Decoding untrusted input
Checks the payload on decodeβœ… the wire checked by default⚠️ syntax only: SyntaxError27❌ no decoder: JSON.parse ignores the schema28❌ bad meta β†’ raw TypeError, Invalid Date29⚠️ indices and tags checked; some raw errors30❌ none; raw errors or garbage31❌ none; undefined or raw errors32βœ… bounds, wire types and UTF-8 checked⚠️ bounds and UTF-8 checked; wire type ignored33βœ… bounds and wire types checked; UTF-8 notβœ… truncated buffer, bad union/enum index⚠️ InputBufferUnderrunError; wrong-type fields skipped34❌ no verifier in JS: garbage or defaults35⚠️ pointer and bounds checks; some raw RangeErrors36❌ no bounds checks: garbage or raw RangeError37βœ… json.assertParse, protobuf.assertDecode❌ none; Node RangeErrors or garbage38⚠️ mostly Errors; some raw TypeErrors39βœ… DecodeError, or RangeError on truncation⚠️ mostly Errors; some raw TypeErrors40βœ… BSONError on every corruption tried⚠️ format only: one generic Error41❌ truncated binary β†’ null or wrong values42
Errors with a path into the payloadβœ… DecodeError with a path: orders[1].sku⚠️ position in the message⚠️ SyntaxError position only⚠️ SyntaxError position; meta errors none⚠️ message only (Invalid input)❌ raw engine errors❌ raw engine errors⚠️ message with byte offset; verify gives orders.sku⚠️ type and field name, no index⚠️ message only (premature EOF)⚠️ message only (truncated buffer)❌ none; underrun error has no message❌ no decode errors at all⚠️ message with a pointer address, no field path❌ raw errors, no pathβœ… TypeGuardError.path ($input.a[1].b)❌ raw Node errors⚠️ message only, no path⚠️ message only (byte offsets), no path⚠️ message only, no path⚠️ message only; some name the field❌ no path or offset⚠️ message only (expected ',' or '}')
Deeply nested inputβœ… stack overflow β†’ DecodeError; maxDepth opt-in⚠️ no limit: 1 000 000 levels parse43⚠️ decode is JSON.parse: no limit44❌ raw RangeError from a deep meta tree45❌ raw stack overflow at ~2,200 levels46βœ… not recursive; 100 000 levels decode❌ raw stack overflow at ~3,200 levels47βœ… recursionLimit 100: max depth exceededβœ… recursionLimit 100 on fromBinary/fromJsonβœ… 100 levels on decode, hard-coded❌ raw RangeError at 100 000 levels48βœ… 64 levels (DEFAULT_RECURSION_DEPTH)❌ object API unpack(): raw stack overflow49⚠️ lazy reads; deep copy β†’ raw RangeError50❌ raw stack overflow (100 000 levels)❌ recursive generated code: raw stack overflow51β€” no recursive types; depth fixed by schema❌ raw RangeError at 100 000 levels52βœ… iterative decoder: 100 000 levels decode❌ raw RangeError at 100 000 levels53βœ… iterative parser: 100 000 levels decode❌ raw RangeError past ~4 150 levels54❌ raw RangeError at 100 000 levels55
__proto__ keysβœ… keys from the schema; a dictionary's __proto__ stays ownβœ… own property, prototype untouchedβœ… JSON.parse keeps an own key⚠️ own key; a registered class's prototype replaced56βœ… rejected (__proto__ key throws)❌ __proto__ header key sets the prototype57❌ __proto__ key sets the prototype58βœ… __proto__ map key becomes an own propertyβœ… own property (setOwn) in maps and Struct❌ __proto__ map key sets the prototype59❌ map key __proto__ sets the prototype60βœ… keys from the IDL; map __proto__ stays a keyβœ… keys from the schemaβœ… keys from the schemaβœ… keys from the schema❌ protobuf Record key __proto__ sets the prototype61βœ… keys from the schemaβœ… safe: __proto__ renamed to __proto_βœ… safe: __proto__ key rejected (DecodeError)βœ… safe: __proto__ renamed to __proto_βœ… safe: own property via definePropertyβœ… own property, prototype untouched⚠️ dom.Struct safe; toJSON() sets the prototype62
Size limits⚠️ maxLength on streams, maxDepth63❌ none (engine string limit only)❌ none (maxDepth is the schema's)❌ none❌ no size or depth options64❌ none❌ none; shared refs expand exponentially65⚠️ depth only (Reader.recursionLimit)66βœ… recursionLimit; readMaxBytes 64 MiB on streams⚠️ depth only (100, fixed)❌ none67βœ… depth 64; server frame maxLength❌ none (builder capped at 2 GB)⚠️ 64 MiB traversal limit, not configurable68❌ none: 13 bytes β†’ 10^8-element array69❌ none❌ none; declared lengths trusted70⚠️ stream maxIncompleteBufferSize only71βœ… maxStrLength, maxBinLength, maxArrayLength, maxMapLength, maxExtLength⚠️ setSizeLimits: array and map sizes (global)72❌ none (size header must match the buffer)❌ none❌ none
Payloads and layouts
Columnar layout for listsβœ… layout: 'columns' (and flat)❌ none❌ none❌ none❌ none❌ row-major values after the header❌ none❌ none❌ none❌ none❌ row-wise only❌ row-wise only❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ row-wise only
What an absent optional field costsβœ… sparse: a presence mask, nothing per absent fieldβœ… absent keys cost nothingβœ… absent keys omittedβœ… absent keys omittedβœ… nothing (keys omitted)⚠️ null placeholder per absent fieldβœ… nothing (keys omitted)βœ… nothing: unset fields are omittedβœ… nothing: unset fields are omittedβœ… nothing: unset fields are omitted⚠️ a byte each (null union branch)βœ… nothing (unset ids omitted)⚠️ 2-byte vtable slot up to last present field⚠️ fixed slots; packing squeezes zerosβœ… messages: absent fields cost nothingβœ… nothing: absent keys and tags omitted❌ no absent fieldsβœ… nothing (key omitted)βœ… nothing (key omitted)βœ… nothing (key omitted)βœ… nothing (key omitted)βœ… absent keys cost nothingβœ… nothing (absent fields omitted)
Repeated strings or structures written onceβœ… intern: a string table per message❌ none❌ none⚠️ dedupe: true writes a shared object onceβœ… equal values and references written onceβœ… key header once per collectionβœ… every value and key list stored once❌ none❌ none❌ none❌ none (strings repeat)❌ noneβœ… createSharedString; vtables deduplicated❌ none❌ none❌ none❌ noneβœ… records: keys once per structure❌ none (key cache is decode-side only)βœ… records + pack (packed CBOR, tag 51)❌ none⚠️ a shared object written once (back-reference)βœ… symbol tables (binary)
Delta encodingβœ… delta columns of differences❌ none❌ none❌ none❌ none❌ none❌ none❌ none (zigzag sint is not delta)❌ none (zigzag sint is not delta)❌ none (zigzag sint is not delta)❌ none❌ none (Compact deltas field ids only)❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none❌ none
Tooling
Streaming decodeβœ… NDJSON, TransformStreams, parseItems (./stream)❌ none (NDJSON by hand)❌ none❌ none❌ none (stringifyAsync awaits promises only)❌ none❌ none⚠️ encodeDelimited/decodeDelimited onlyβœ… sizeDelimitedDecodeStream (async iterable)⚠️ useAsyncIterable transforms; no framingβœ… BlockDecoder, RawDecoder (Node streams)⚠️ framed/buffered transports (length-prefixed)⚠️ size-prefixed buffers (finishSizePrefixed) only❌ no stream reader (segment framing only)⚠️ length-prefixed messages and unions only❌ none❌ noneβœ… UnpackrStream, decodeIter (Node streams)βœ… decodeMultiStream, decodeArrayStream, decodeAsyncβœ… DecoderStream, decodeIter, encodeAsAsyncIterable⚠️ deserializeStream over one buffer only❌ none⚠️ Reader steps through values; whole input in memory
Code generated ahead of timeβœ… toSource: standalone modules (./standalone)β€” nothing to compileβœ… mode: 'standalone' writes a CJS moduleβ€” nothing to compileβ€” nothing to compileβ€” nothing to compileβ€” nothing to compileβœ… pbjs -t static-module + pbtsβœ… protoc-gen-es: embedded descriptor + typesβœ… generated .ts per .proto❌ runtime new Function onlyβœ… thrift --gen js:nodeβœ… flatc --ts (--gen-object-api)βœ… capnp-es plugin writes .js, .ts, .d.tsβœ… bebopc writes TSβœ… the ttsc transform inlines codecs at build❌ runtime compile onlyβ€” nothing to compileβ€” nothing to compileβ€” nothing to compileβ€” nothing to compileβ€” nothing to compileβ€” nothing to compile
CLIβœ… protoarray compile❌ none❌ none❌ none❌ none❌ none❌ noneβœ… pbjs, pbts (protobufjs-cli)βœ… protoc-gen-es plugin via buf/protocβœ… protoc-gen-ts_proto plugin via protoc/buf❌ noneβœ… thrift compiler (native, separate install)βœ… flatc (native binary, not on npm)βœ… capnp-es (shells out to C++ capnpc)βœ… bebopc (npm bebop-tools)βœ… ttsc / ttsx (official peer, samchon/ttsc)❌ none❌ none❌ none❌ none❌ none❌ noneβœ… ion (amazon-ion/ion-cli, Rust)

βœ… built in, or in an official package of the same project (named) Β· ⚠️ partial, opt-in, with a caveat, or through a third-party package (named) Β· ❌ not built in Β· πŸ”œ planned by the maintainers, no code yet Β· β€” does not apply. Every cell was checked against the source of the published package in October 2026.

  1. capnp-es is the maintained fork (unjs) of capnp-ts; its README calls it alpha quality. capnp-ts itself was revived in 2026 (0.9.3).
  2. The browser builds in etc/browser/ still require('buffer'), util and stream; the README assumes browserify. It cannot run where new Function is blocked (see noEval).
  3. The browser field maps to lib/nodejs/lib/thrift/browser.js, which still requires util (thrift.js) and uses Buffer in every protocol; the README suggests webpack.
  4. require('buffer').Buffer (schemapack.js:6); the README relies on browserify/webpack 4 adding the shim, which webpack 5 no longer does.
  5. index.js:470 compiles every serializer (EvalError in a realm without code generation). A mode: 'standalone' module runs there, but anyOf/oneOf/if also need inlineValidators: true or Ajv compiles at runtime.
  6. Type.encode/decode/verify and the message constructor are compiled by util.codegen (src/util/codegen.js:53,55, Function(source)); with --disallow-code-generation-from-strings reflection throws EvalError, while a pbjs -t static-module file on protobufjs/minimal runs.
  7. lib/types.js:732 (every type's branch constructor), 2200, 2275, 2295, 2310, 2342, 2434 (record constructor, checker, reader, skipper, writer, resolver). Under node --disallow-code-generation-from-strings even require('avsc') throws EvalError (lib/containers.js:26 builds a type at load). No no-eval mode exists.
  8. schemapack.js:375-376; under --disallow-code-generation-from-strings build throws EvalError (tested).
  9. From the 4th read of a record structure the reader is compiled with new Function (unpack.js:501), also by the default unpack on a payload that defines records in-message. A throw is caught and code generation is switched off for the process (still a CSP violation report). msgpackr/index-no-eval (a UMD build, dist/index-no-eval.cjs) never calls it.
  10. decode.js:47 runs new Function('') when the module loads (a CSP report even if records are never used) and compiles record readers from the 4th read (decode.js:532). If the probe throws, records use the slow path. cbor-x/index-no-eval never calls it.
  11. The README's security notice: schemas are compiled into code, so a schema from a peer is not safe to build.
  12. protoarray's encode trusts its values and checks only what the wire is built from (shapes, a union's discriminator, a list of differences); validating the values is metaschema's, on the same definition, so its cost is opt-in.
  13. Only required and array shapes throw. pattern, minimum, enum and format are ignored, 'false' in a boolean β†’ true, and a const field writes the constant whatever the value.
  14. toBinary throws on "abc", 3.7 or 2^40 in an int32 (cannot encode field t.Msg.age to binary: invalid int32: 3.7) but writes a number in a string field as "42"; create() checks nothing; reflect().set type-checks; rule validation is the separate @bufbuild/protovalidate.
  15. encode throws on "abc" or 3.7 in an int32 (invalid int32: NaN) but writes a number in a string field as "42"; fromJSON coerces (age: "abc" β†’ NaN).
  16. Generated write() calls output.writeI32(this.x) unchecked (binary.js writeI32 uses bit operations; 1.7 β†’ 1). Required fields are checked only by the constructor when args is given; writeString(42) throws.
  17. Tested: "abc" in a short reads back 0, 70000 reads back 4464, 1.9 reads 1. Only (required) presence is checked, by builder.requiredField in the generated endX().
  18. Tested: an object in a Text field is stored as "[object Object]", null as "null"; an Int64 setter throws a raw TypeError for a number.
  19. Tested on the generated-code shape: a missing struct field writes 0, a number in a string field writes "", null throws a raw TypeError. The dynamic RecordWriter checks shapes, enums and missing fields, not scalar types.
  20. build(schema, false) drops them: a string in a uint8 field then encodes as 0. With checks on, 3.7 in an int field becomes 3, NaN passes float checks, a missing string throws Node's ERR_INVALID_ARG_TYPE.
  21. uneval writes JavaScript source text instead. The README lists stability of the format between devalue versions as a non-goal.
  22. README lists human-readable output as a non-goal: {a:'x'} travels as [{"a":1},"x"].
  23. No port found; the format is not specified and may change between versions (README non-goals).
  24. Python in the same repo (PyPI compress-json-python); PHP and Rust are third-party (README).
  25. Ordinary typed arrays serialize their whole backing ArrayBuffer, bytes outside a subarray included (README); Node Buffers are copied first (src/operations.js:83).
  26. undefined, null and a missing key travel as null and decode as null (or the default): every key of the schema is present after decode.
  27. Any well-formed value comes back; checking its shape is left to the program.
  28. Validate with Ajv (already a dependency) against the same schema.
  29. 5 annotated map throws TypeError: number 5 is not iterable; a garbage Date string becomes Invalid Date; the text null throws a destructuring TypeError.
  30. Bad indices and unknown tags throw Error('Invalid input')/Unknown type, but ["BigInt","abc"], ["RegExp","("], ["URL","x"] throw raw SyntaxError/TypeError, Temporal.* tags throw ReferenceError on Node 24, and ["Date",5] is accepted as a Date.
  31. A row missing a value throws RangeError: Invalid array length; unpack('abc') returns [], a string key count returns [ <1 empty item> ], null throws a TypeError.
  32. Out-of-range or invalid refs decode to undefined silently, a self-reference overflows the stack, decompress(null) throws c is not iterable.
  33. Truncation throws RangeError: premature EOF; but a known scalar field is read by its schema type whatever wire type arrives (dist/esm/from-binary.js:168-183): an int32 writer's 08 02 10 03 decodes as name: "\u0010\u0003", age: 0, silently.
  34. Truncation, negative sizes and bad type ids (Invalid type: 127) are errors; a missing required field is not (it reads as null).
  35. Tested: a buffer cut in half decodes to { hp: 0, name: "" }, an out-of-bounds root offset to all defaults, no error. ByteBuffer reads bytes_[i] without bounds checks (ts/byte-buffer.ts); the C++ Verifier is not ported.
  36. Library errors (CAPNP-TS019, TS024) for bad pointers, but an empty buffer and a huge Text length throw raw RangeErrors, and the frame header is trusted before its size check (see limits).
  37. Tested: a truncated buffer throws DataView RangeError; a string length past the end decodes to "A\u0000".
  38. A truncated buffer throws Node's ERR_OUT_OF_RANGE or silently shortens the last string; a buffer field whose length prefix exceeds the payload returns stale process memory (allocUnsafe, schemapack.js:86; tested).
  39. Truncation gives "Unexpected end of MessagePack data". In 3 000 random byte mutations 1–5% escaped as raw TypeError/RangeError/SyntaxError (e.g. read(...).map is not a function, a pointer ext without a reference map); the reserved byte 0xC1 decodes to a C1 sentinel object instead of an error.
  40. Truncation gives "Unexpected end of CBOR data". In 3 000 random byte mutations 5–7% escaped as raw TypeError/RangeError (e.g. "Cannot create property 'slowReads' on string", "byte length of Float32Array should be a multiple of 4"); a record key that is a BigInt reaches JSON.stringify and throws a TypeError.
  41. Truncated, garbage and huge-length inputs all throw Unable to deserialize cloned data.; there is no shape to check.
  42. IonSpan.js:165-170 returns EOF (-1) past the end instead of throwing: a 55-byte value cut to 13-27 bytes loads as null, cut to 52 bytes loads with a different timestamp. The text parser rejects truncated input.
  43. V8's JSON.parse does not recurse, but JSON.stringify of the result throws RangeError past ~6 200 levels, as may any recursive consumer.
  44. maxDepth (default 100) limits the schema, not data; encoding a 100 000-deep recursive value throws a raw RangeError.
  45. Deep json alone parses (JSON.parse); a 100 000-deep meta.values tree overflows the recursive traverse (dist/plainer.js:8).
  46. hydrate (src/parse.js:71) recurses: a 14 KB flat payload of ~2,200 nested arrays (Node 24 default stack) throws a raw RangeError: Maximum call stack size exceeded; stringify and uneval overflow too on deep values.
  47. decode ↔ decodeArray/decodeObject recurse (dist/core.js:16-51); a chain of ~3,200 nested arrays throws a raw RangeError on Node 24.
  48. A 100 001-byte recursive list throws Maximum call stack size exceeded from fromBuffer; toBuffer/isValid of a deep or cyclic value overflow the same way.
  49. Accessors are lazy and do not recurse, but generated unpack() does: a 100 000-deep chain throws RangeError, and a crafted negative uoffset (accepted: readInt32 is signed) makes a cycle that overflows too.
  50. Reads do not recurse and the traversal limit stops a pointer cycle (CAPNP-TS022), but the nesting limit defaults to MAX_INT32: setRoot/copyFrom of a 100 000-deep message overflows the stack.
  51. Not run through the transform (it needs ttsc): a decoder transcribed from ProtobufDecodeProgrammer.go overflows at 100 000 levels; no depth option exists.
  52. Recursive read() with no depth limit. checkedRead marks any RangeError incomplete (unpack.js:225), so UnpackrStream treats a deep message as a partial one: it emits nothing, buffers, and stalls until maxIncompleteBufferSize (64 MB) is exceeded. pack of a deep value also overflows.
  53. Recursive read() with no depth limit. checkedRead marks any RangeError incomplete (decode.js:239), so DecoderStream treats a deep message as a partial one: it emits nothing and keeps buffering, with no size cap. encode of a deep value also overflows.
  54. Measured on a hand-built nested array with the default stack; v8.serialize itself throws RangeError past ~3 280 levels.
  55. load of 100 000 nested lists or s-expressions, and dumpText/dumpBinary of a deep or cyclic value, throw Maximum call stack size exceeded. The raw Reader cursor steps in 100 000 levels fine.
  56. Plain objects are safe and meta paths through proto are rejected, but a value typed as a registered class is built with Object.assign (dist/transformer.js:138), whose proto setter replaces the instance's prototype.
  57. o[hlist[++ki]] = hlist[i++] on a literal {} (js/jsonh.js:64): [1,"proto",{"isAdmin":true}] decodes to a record that inherits isAdmin (tested).
  58. o[k] = v on a literal {} (dist/core.js:33): a payload with key proto yields an object that inherits the attacker's properties (tested).
  59. Binary decode assigns map entries with obj[key] = value (build/src/main.js:1223): a map<string, Item> entry keyed proto makes the decoded map inherit from the attacker's message; Struct keys hit the same path (with useMapType the decoded Struct object's prototype is replaced). fromJSON uses defineProperty and is safe.
  60. lib/types.js:1833-1838 assigns val[key] on {}: the key disappears and the decoded map inherits the payload's object (same in fromString). No global pollution.
  61. Generated decode assigns output[entry.key] = entry.value on {} (native/core/programmers/protobuf/ProtobufDecodeProgrammer.go:379-397); reproduced with the real _ProtobufReader. The JSON path parses with JSON.parse and is safe.
  62. Fields live in Object.create(null) (dom/Struct.js:10), so get('proto') works and nothing global is touched. toJSON() copies them onto Object.create(Struct.prototype) with obj[key] =: proto replaces the prototype and the fields after it vanish (JSON.stringify gives {}).
  63. parse reads a whole text with JSON.parse, so the text a program accepts is its own limit; the stream parsers hold at most maxLength characters of one item (16 MiB by default).
  64. A sparse array's declared length is not allocated (src/operations.js:181), so [[-7,4294967295]] is cheap.
  65. A 171-byte payload of arrays that each reference the previous one twice decompresses to 6.3 MB of JSON (20 levels); each level doubles it.
  66. No message, string or array size limits.
  67. A 4-byte payload decodes into an array of 1 000 000 nulls (array<null> with a count only).
  68. It counts pointer objects per Message, so one legit Message re-read ~4.2 M times throws. The segment count in the frame header is allocated (Array.from, BlZmb8ia.mjs:818) before it is checked: 8 bytes claiming 2^32-1 segments start a multi-billion-element allocation.
  69. A bool[] length prefix is trusted: 13 bytes decoded into a 100 000 000-element array in 1.2 s (up to 2^32 possible).
  70. A 4-byte payload for a buffer field yields a 268 MB Buffer.
  71. Array/map lengths are checked against the remaining bytes (no allocation from a small header), but there is no option for string, array, map or depth limits.
  72. Process-wide, not per decoder; defaults 112 810 000 / 16 810 000. maxObjectSize, documented in the README, is never read by the decoder, record objects bypass maxMapSize, and there is no string or depth limit.

Exports

const {
  Schema,
  encode, decode, stringify, parse, encodeEach, decodeEach,
  SchemaDefinitionError, EncodeError, DecodeError,
} = require('@alexify/protoarray');
const { resolve } = require('@alexify/protoarray/resolve');
const { SchemaRegistry } = require('@alexify/protoarray/registry');
const { parseLines, parseItems /* … */ } = require('@alexify/protoarray/stream');
const { toSource } = require('@alexify/protoarray/standalone');
const { compatible, baseline, restore } = require('@alexify/protoarray/evolve');
const { toBinary, fromBinary } = require('@alexify/protoarray/binary');
import { Schema, type Decoded, type Snapshot } from '@alexify/protoarray';

The API reference lists every member.

Changelog

See CHANGELOG.md.

License

MIT Β© Alexis Technologies

About

Fast, zero-dependency schema-based compact serialization for Node.js and browsers: objects travel as positional JSON arrays, a shared schema maps every position back to its key. A lightweight alternative to Protocol Buffers

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages