@cauldria.dev/nbt
v0.1.0
Published
Quick-and-dirty Named Binary Tag (NBT) codec
Readme
nbt
Quick-and-dirty Named Binary Tag codec (NBT) — the NBT analogue of the built-in
JSON module. Java-edition wire format (big-endian, named tags), plus SNBT
("stringified NBT") parsing and printing.
const NBT = require("@cauldria/nbt");
NBT.decode(nbt); // Uint8Array -> JavaScript value (gunzips gzipped input)
NBT.encode(value, schema); // JavaScript value -> Uint8Array (raw, empty root name)
NBT.parse(snbt); // SNBT string -> JavaScript value
NBT.stringify(value, schema); // JavaScript value -> SNBT string (4-space pretty)Run the tests: npm test in nbt/ — ESLint first, then the Jest suites (nbt.test.js, cli.test.js).
Value <-> tag mapping
| NBT tag | JavaScript |
| --- | --- |
| TAG_Byte / TAG_Short / TAG_Int / TAG_Float / TAG_Double | number |
| TAG_Long | BigInt |
| TAG_ByteArray / TAG_IntArray / TAG_LongArray | Int8Array / Int32Array / BigInt64Array |
| TAG_String | string |
| TAG_List | Array (homogeneous) |
| TAG_Compound | plain object |
Type inference (no schema)
boolean → byte, BigInt → long, string → string, integer in int32 range
→ int, integer outside → long, fractional → double, typed arrays → their
matching array tags, plain Array → list (elements must agree on one type),
plain object → compound.
Use a schema or wrapper values whenever inference is wrong for you — floats, bytes stored as ints, lists of a specific element type, etc.
Schemas
A schema mirrors the value's structure with tag types at the leaves:
NBT.encode(
{ pos: { x: 1, y: 2 }, items: [{ id: 3n }] },
{ pos: { x: "TAG_Double", y: "TAG_Double" }, items: "TAG_Compound" }
);Each leaf may be a tag name string ("TAG_Byte", "TAG_Byte_Array", ...,
case/underscore insensitive) or a tag class (NBT.NBTByte, NBT.NBTCompound,
...). An object leaf is a nested compound schema; a non-list tag applied to an
array value means "a list OF that type".
Wrapper values
NBT.byte(v), NBT.short(v), NBT.int(v), NBT.long(v), NBT.float(v),
NBT.double(v), NBT.byteArray(v), NBT.string(v), NBT.list(v),
NBT.compound(v), NBT.intArray(v), NBT.longArray(v) box a value with its
tag type — no schema entry needed.
SNBT notes
parse() supports quoted keys and strings (' or "), escapes (\', \\,
\n, \t), number suffixes (1b 1s 1 1l 1.0f 1.0d), typed arrays
([B; ...], [I; ...], [L; ...]), lists, nested compounds, true/false
(encoding as bytes, vanilla-style), trailing commas, and // + /* */
comments. stringify() pretty-prints in the same style as the files in
samples/.
Compressed files (.dat / .nbt)
decode() transparently decompresses gzip and zlib wrapped input, and
NBT.detectCompression(bytes) reports which container a byte array is
("gzip", "zlib", or "raw"). encode() always returns raw NBT — use
zlib.gzipSync/deflateSync (or the CLI) to compress the output.
CLI (manual debugging)
node cli.js dump <file> [-o out] [--json] # any format -> SNBT (or JSON)
node cli.js pack <in> -o <out> # -> binary; .dat -> gzip, .nbt -> raw
node cli.js merge <a> <b> -o <out> # deep-merge two files (compounds recurse, b wins)
node cli.js extract <file> --name <tag> # one named tag as its own file (default: <tag>.nbt)
node cli.js inspect <file> # container report + real wire tag typesEvery in/out file may be .snbt, .json or any binary container
(.nbt/.dat, raw, gzipped or zlib-compressed) — compression is detected
from the bytes, and binary outputs compress by extension (.dat -> gzip,
.nbt -> raw) unless --raw/--gzip/--zlib say otherwise.
JSON support
dump --json (or any .json output) writes plain JSON: longs become
SNBT-style "123l" strings, typed arrays become plain lists. Every command
that reads input (pack, merge, extract, ...) accepts .json back with
the same convention, so longs round trip losslessly; typed arrays degrade to
plain lists — use .snbt or binary when the exact array tag type matters.
