Zero-dependency JavaScript/TypeScript library for ASUN (Array-Schema Unified Notation) — a token-efficient, schema-driven data format for LLM interactions and large-scale data transfer.
@athanx/asun is the official runtime for both JavaScript and TypeScript users. It ships ESM/CJS builds and bundled .d.ts type declarations in a single package, so there is no separate asun-ts package to install.
Works in browsers, Node.js, Deno, Bun and any JS framework: Vue, React, Svelte, SolidJS, etc.
Legacy <k:v> map syntax is no longer supported. Key-value collections should be modeled as entry lists such as attrs@[{key@str,value@int}].
ASUN separates schema from data, eliminating repeated key names found in JSON. The schema is declared once; each data row carries only values:
JSON (100 tokens):
{"users":[{"id":1,"name":"Alice","active":true},{"id":2,"name":"Bob","active":false}]}
ASUN (~35 tokens, 65% saved):
[{id@int, name@str, active@bool}]:(1,Alice,true),(2,Bob,false)
| Aspect | JSON | ASUN |
|---|---|---|
| Token efficiency | 100% | 30–70% ✓ |
| Key repetition | Every object | Declared once ✓ |
| Human readable | Yes | Yes ✓ |
| Type annotations | None | Built-in ✓ |
| Data size | 100% | 40–55% ✓ |
npm install @athanx/asunOr copy dist/asun.min.js directly into a web page (exposes a global ASUN object).
TypeScript users do not need a separate stub package. npm run build emits dist/index.d.ts, and the package exports it via the types field.
import {
encode,
encodeTyped,
encodePretty,
encodePrettyTyped,
decode,
encodeBinary,
decodeBinary,
} from "@athanx/asun";
const users = [
{ id: 1, name: "Alice", score: 9.5 },
{ id: 2, name: "Bob", score: 7.2 },
];
// Schema is inferred automatically — no schema string needed
const text = encode(users); // schema without scalar hints
const textTyped = encodeTyped(users); // schema with scalar hints (use for typed round-trip)
const pretty = encodePretty(users); // pretty + untyped
const prettyTyped = encodePrettyTyped(users); // pretty + scalar hints
const blob = encodeBinary(users); // binary (schema inferred internally)
console.log(decode(textTyped)); // original array restored
console.log(decode(prettyTyped)); // same from pretty
console.log(decodeBinary(blob, "[{id@int, name@str, score@float}]")); // binary decodeNote on
encodevsencodeTypedencode(obj)emits a schema without scalar hints ({id,name}) so the output is shorter. When decoded, all values without explicit types are returned as strings. UseencodeTyped(obj)when you need scalar hints to preserve numeric and boolean types on round-trip.
| JS value | Inferred ASUN type |
|---|---|
whole number |
int |
fractional number |
float |
true / false |
bool |
| text | str |
null / undefined |
str? (optional) |
Note: Schema is inferred from the first element of an array. To make a field optional (
str?), ensure the first element hasnullfor that field.
Serialize a plain object or array to ASUN text with an inferred schema without scalar hints. When decoded, all scalar fields without explicit hints come back as strings:
encode({ id: 1, name: "Alice" });
// → '{id,name}:\n(1,Alice)\n'
encode([{ id: 1 }, { id: 2 }]);
// → '[{id}]:\n(1),\n(2)\n'
decode(encode({ id: 1, name: "Alice" }));
// → { id: '1', name: 'Alice' } ← all strings when scalar hints are omittedUse encodeTyped when you need decode to restore the original types.
Same as encode but emits an inferred schema with scalar hints. Use this when you want decode() to restore the original scalar types:
encodeTyped({ id: 1, name: "Alice", active: true });
// → '{id@int,name@str,active@bool}:\n(1,Alice,true)\n'Nested object and array fields keep structural bindings even when scalar hints are omitted:
encode({
profile: { host: "127.0.0.1", port: 8080 },
tags: ["blue", "fast"],
});
// → '{profile@{host,port},tags@[]}:\n((127.0.0.1,8080),[blue, fast])\n'Pretty-printed ASUN text with inferred untyped schema.
Pretty-printed ASUN text with inferred typed schema.
Deserialize ASUN text. The schema is embedded in the text itself:
const rec = decode("{id@int, name@str}:\n(1,Alice)\n");
const rows = decode("[{id@int, name@str}]:\n(1,Alice),\n(2,Bob)\n");Serialize to binary format. Schema is inferred internally — no schema string needed:
const data = encodeBinary(rows);Deserialize from binary format. Schema is required because the binary wire format carries no embedded type information:
const rows = decodeBinary(data, "[{id@int, name@str}]");| Schema type | JS value | Example |
|---|---|---|
int |
number (integer) | 42, -100 |
uint |
number (non-negative integer) | 0, 9007199254740991 |
float |
number | 3.14, -0.5 |
bool |
true / false |
true, false |
str |
text | Alice, "Carol Smith" |
T? |
value or null |
hello / null |
Optional fields: append ? to any type (str?, int?, float?, bool?, uint?).
<script src="dist/asun.min.js"></script>
<script>
const text = ASUN.encodeTyped([{ id: 1, name: "Alice" }]);
console.log(ASUN.decode(text));
</script><script type="module">
import { encodeTyped, decode } from "./dist/index.js";
const text = encodeTyped([{ id: 1, name: "Alice" }]);
console.log(decode(text));
</script>Works as a regular npm package — just import and use:
// Vue composable example
import { encodeTyped, decode } from "@athanx/asun";
export function useAsun() {
const serialize = (data: object[]) => encodeTyped(data);
const deserialize = (text: string) => decode(text);
return { serialize, deserialize };
}Little-endian layout, byte-identical to asun-rs and asun-go:
| Type | Bytes |
|---|---|
int |
8 (i64 LE) |
uint |
8 (u64 LE) |
float |
8 (f64 LE) |
bool |
1 |
str |
4-byte length LE + UTF-8 bytes |
| optional | 1-byte tag (0=null, 1=present) + value |
| slice | 4-byte count LE + elements |
npm install
npm run build # generates dist/index.js, dist/index.cjs, dist/index.d.ts, dist/asun.min.js
npm test # vitestnode examples/basic.js # 9 scenarios, basic usage
node examples/complex.js # complex nested scenarios and legacy-syntax rejection
node examples/bench.js # performance vs JSON.parse / JSON.stringifyASUN JS produces 50–55% smaller output than JSON. Because JSON.parse / JSON.stringify are implemented in C, raw speed is slower — but:
- Network bandwidth is typically the bottleneck — 50% smaller payload saves more time than parse overhead costs
- LLM token cost — 30–70% fewer tokens = lower API cost and faster responses
- Binary format —
encodeBinary/decodeBinaryis fastest for machine-to-machine transfer
For latency-sensitive hot paths, use the Rust or Go implementations if your stack supports them.
MIT
Measured on this machine with Node 24.14.0.
Headline numbers:
- Flat 1,000-record dataset: ASUN text
58,539 Bvs JSON121,451 B(51.8%smaller) - Flat 5,000-record dataset: ASUN serialize
8.93msvs JSON13.27ms, but deserialize20.10msvs JSON16.92ms - Large 10,000-record dataset: ASUN serialize
37.86msvs JSON20.23ms, deserialize37.25msvs JSON33.82ms - Throughput summary on 10,000-record-style text path: ASUN text serialize ran at
0.30 M records/svs JSON0.75 M-class baseline, and deserialize was roughly at parity in this run - Binary path is mainly useful for decode and transport size here: on 1,000 records, BIN deserialize
4.68msvs JSON2.08ms, with payload72,784 Bvs JSON121,451 B