npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@retrovm/cybon

v0.5.0

Published

Compact binary serialization format (Cybon) encoder/decoder for TypeScript

Readme

@retrovm/cybon

TypeScript implementation of Cybon, a compact binary serialization format similar in spirit to JSON, with two extras: real 64-bit integers and in-document references (so shared or circular object graphs serialize without duplication or infinite loops). Also includes Cyton, its human-readable textual twin (same data model, JSON-like text).

Ported from the reference C implementation (fl_cybon.h / fl_cyton.h). Pure standard JS (Uint8Array, DataView, TextEncoder/TextDecoder) — no Node or Bun-specific APIs, so it runs in Node, Bun, Deno, and browsers alike.

Install

npm install @retrovm/cybon

Usage

import { CybonDecoder, CybonEncoder } from "@retrovm/cybon";

const bytes = new CybonEncoder().encode({
  name: "cybon",
  version: 1,
  tags: ["binary", "compact"],
  data: new Uint8Array([1, 2, 3]),
});

const value = new CybonDecoder(bytes).decode();

CybonEncoder/CybonDecoder also expose the item-level building blocks the high-level encode()/decode() methods are built on (beginArray/addString/end, unsigned/string/object/resolve, ...) for anyone who wants to write or walk a buffer by hand instead of going through a CybonValue.

Supported types

| TypeScript | Cybon wire type | | ---------------------------------- | ---------------- | | number | NUMBER (smallest integer width that fits, or float) | | bigint | NUMBER (64-bit integer) | | string | STRING (UTF-8) | | boolean | MARK (true/false) | | null | MARK (null) | | Uint8Array | BINARY | | Array<CybonValue> | ARRAY | | { [key: string]: CybonValue } | OBJECT (string keys) | | Map<CybonKey, CybonValue> | OBJECT (string, number, or bigint keys) |

Notes:

  • Integers that fit in Number.isSafeInteger range decode back as number; larger 64-bit integers decode as bigint. Encoding a bigint outside the 64-bit signed/unsigned range throws.
  • Non-integer or unsafe-magnitude numbers are encoded as F32 (if exactly representable) or F64.
  • undefined object properties are omitted (matching JSON.stringify); undefined array elements are encoded as null.
  • The wire format has no int/bigint/float-width distinction beyond value rangeencode()/decode() (and the Cyton methods below) are round-trip-value-safe, not round-trip-byte-safe: encoding 5n and decoding it back gives 5 (a number), and the original wire width (e.g. U8 vs U16 vs inline) isn't preserved. Don't rely on re-encoded output being byte-identical to the original (e.g. for hashing or signing) even when the value round-trips exactly.
  • Object keys: Cybon supports NUMBER or STRING keys. A plain { [key: string]: CybonValue } object only ever has string keys — this is what encode() produces for a plain JS object, and what decode() produces for a document whose object keys are all STRING. A document with at least one NUMBER key decodes as a Map<CybonKey, CybonValue> instead, so a numeric key never collides with a same-valued string key. To encode a NUMBER key yourself, pass a Map.
  • A single buffer/text document holds exactly one top-level value; decode()/fromCyton() throw on trailing data. Cybon buffers are concatenable (several documents back-to-back) — use decodeStream(): CybonValue[] to read all of them.
  • EXT items (reserved for future extension types) decode as opaque Uint8Array, same as BINARY, instead of failing — matching the format's guarantee that EXT is always safely skippable by a reader that doesn't understand it.
  • A REF can't point at another REF; that's rejected as malformed rather than followed.

Shared and circular references

If the same object or array instance (===) appears more than once in the value you encode, it is written once and referenced afterwards via Cybon's REF item — this also means circular structures (an object that (indirectly) contains itself) can be encoded without infinite recursion. Decoding restores the same object identity at every place the reference occurred.

const shared = { id: 1 };
const value = { a: shared, b: shared };

const decoded = new CybonDecoder(new CybonEncoder().encode(value)).decode() as { a: object; b: object };
decoded.a === decoded.b; // true

Numeric object keys

import { CybonDecoder, CybonEncoder } from "@retrovm/cybon";

const map = new Map<string | number, unknown>([[5, "five"], ["five", "the word"]]);
const decoded = new CybonDecoder(new CybonEncoder().encode(map)).decode() as Map<unknown, unknown>;
decoded.get(5);      // "five"
decoded.get("five"); // "the word"

Concatenated documents

import { CybonDecoder, CybonEncoder } from "@retrovm/cybon";

const enc = new CybonEncoder();
const bytes = new Uint8Array([...enc.encode(1), ...enc.encode("two"), ...enc.encode([3])]);
new CybonDecoder(bytes).decodeStream(); // [1, "two", [3]]

(enc.encode() resets the encoder's buffer on every call, so the same instance can be reused - each call above returns its own standalone document, concatenated by hand into one buffer.)

Cyton (text format)

Cyton is a pure binary↔text bridge: CybonEncoder.prototype.toCyton() converts the buffer an encoder has already built to Cyton text, and CybonDecoder.fromCyton() (static) parses Cyton text back into a Cybon binary buffer - decode that buffer normally to get a CybonValue.

import { CybonDecoder, CybonEncoder } from "@retrovm/cybon";

const enc = new CybonEncoder();
enc.encode({ name: "cybon", data: new Uint8Array([1, 2, 3]) });
const text = enc.toCyton({ pretty: true });

const value = new CybonDecoder(CybonDecoder.fromCyton(text)).decode();

Differences from JSON:

  • Binary values are written as <base64...> (angle brackets, no quotes).
  • Shared/circular references are written as a dot-chain path prefixed with @ (absolute, from the document root), e.g. @"a".[0]; an empty path (just @) means "self". toCyton() emits these automatically for repeated object/array identities, exactly like encode() does for the binary format.
  • NaN/Infinity have no literal in the grammar. Encoding one to binary works fine (encode() supports NaN/Infinity floats); toCyton() is what throws, since that's the step that actually can't represent it as text.
  • pretty: true (default false) indents the output multi-line; compact output is a single line.

Errors

CybonDecoder.prototype.decode()/decodeStream() and CybonDecoder.fromCyton() throw CybonDecodeError on truncated, malformed, or otherwise invalid input rather than reading out of bounds or crashing.

Build

bun run build   # emits dist/cybon.js (ESM) and dist/cybon.d.ts
bun test