@cbortech/cbor
v0.27.1
Published
Convert between CBOR, CDN (CBOR-EDN), and JavaScript values with CDDL validation
Maintainers
Readme
@cbortech/cbor
TypeScript library for converting between CBOR, CDN (CBOR-EDN), and JavaScript values, plus parsing, formatting, and validation for CDDL schemas.
A live playground is available at https://cbor.tech/cbor/.

This package exposes the CBOR facade plus separate CDN, CDDL, and AST entrypoints for tooling and extensions. Lower-level parser and encoder internals are not part of the documented public API.
Install
npm install @cbortech/cborFor command-line conversion and inspection, a companion CLI package is available as @cbortech/cbor-cli.
npm install -g @cbortech/cbor-cliFor editor integration, try the companion VS Code extension, which is built with this package.
Import
import { CBOR } from '@cbortech/cbor';Default import is also supported:
import CBOR from '@cbortech/cbor';Quick Examples
JavaScript to CBOR bytes
import { CBOR } from '@cbortech/cbor';
const bytes = CBOR.encode({ hello: 'world', n: 42 });
console.log(bytes);
// Uint8Array(...)CBOR bytes to JavaScript
import { CBOR } from '@cbortech/cbor';
const value = CBOR.decode(
new Uint8Array([
0xa2, 0x65, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x65, 0x77, 0x6f, 0x72, 0x6c,
0x64, 0x61, 0x6e, 0x18, 0x2a,
])
);
console.log(value);
// { hello: 'world', n: 42 }Validate with CDDL
Compile a CDDL schema and validate CBOR bytes, CDN text, or a CborItem AST
against it:
import { CDDL } from '@cbortech/cbor/cddl';
const schema = CDDL.compile('point = { x: int, y: int }');
const result = schema.validate('{"x": 12, "y": -3}');
console.log(result.valid);
// trueCBOR Sequence to JavaScript values
decodeSeq reads concatenated CBOR items as a CBOR Sequence and yields each item as a JavaScript value.
import { CBOR } from '@cbortech/cbor';
const a = CBOR.encode({ id: 1 });
const b = CBOR.encode({ id: 2 });
const seq = new Uint8Array([...a, ...b]);
const values = [...CBOR.decodeSeq(seq)];
// [{ id: 1 }, { id: 2 }]CBOR bytes to CDN
decompile converts CBOR binary data to a CDN text string. It handles CBOR Sequences automatically: multiple concatenated items produce newline-separated CDN output.
import { CBOR } from '@cbortech/cbor';
// Single item
const text = CBOR.decompile(new Uint8Array([0x83, 0x01, 0x02, 0x03]));
console.log(text);
// [1,2,3]
// CBOR Sequence — each item on its own line
const seq = new Uint8Array([...CBOR.encode(1), ...CBOR.encode('two')]);
console.log(CBOR.decompile(seq));
// 1
// "two"CDN to CBOR bytes
compile converts a CDN text string to CBOR binary data. Multi-item CDN Sequences automatically produce a CBOR Sequence (RFC 8742): concatenated items.
import { CBOR } from '@cbortech/cbor';
// Single item
const bytes = CBOR.compile('[1, 2, 3]');
console.log(bytes);
// Uint8Array([0x83, 0x01, 0x02, 0x03])
// CDN Sequence — output is a CBOR Sequence
const seq = CBOR.compile('{"id":1}\n{"id":2}');
console.log([...CBOR.decodeSeq(seq)]);
// [{ id: 1 }, { id: 2 }]CBOR bytes to hex dump
toHex converts CBOR binary data to an annotated hex dump string. CBOR Sequences are handled automatically: each item produces its own dump, separated by newlines.
import { CBOR } from '@cbortech/cbor';
const bytes = CBOR.encode([1, 2, 3]);
console.log(CBOR.toHex(bytes));
// 83 -- Array of length 3
// 01 -- 1
// 02 -- 2
// 03 -- 3Hex dump to CBOR bytes
fromHex parses an annotated hex dump back to CBOR binary data. Multi-item dumps produce a CBOR Sequence (RFC 8742): concatenated items.
import { CBOR } from '@cbortech/cbor';
const bytes = CBOR.fromHex(`
83 -- Array of length 3
01 -- 1
02 -- 2
03 -- 3
`);
console.log([...CBOR.decodeSeq(bytes)]);
// [[1, 2, 3]]JavaScript to CDN
import { CBOR } from '@cbortech/cbor';
const text = CBOR.stringify({ a: 1, b: true, c: null });
console.log(text);
// {"a":1,"b":true,"c":null}Pretty CDN
import { CBOR } from '@cbortech/cbor';
const text = CBOR.stringify({ items: [1, 2, 3], ok: true }, { indent: 2 });
console.log(text);
// {
// "items": [
// 1,
// 2,
// 3
// ],
// "ok": true
// }CDN to JavaScript
import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse("[1, h'deadbeef', true, null]");
console.log(value);
// [1, Uint8Array(...), true, null]CDN Sequence to JavaScript values
parseSeq parses multiple CDN items separated by whitespace, commas, or comments, and also accepts JSONL / NDJSON input.
import { CBOR } from '@cbortech/cbor';
const values = [...CBOR.parseSeq('1 "two" [3]')];
// [1, 'two', [3]]
const jsonl = '{"id":1}\n{"id":2}\n{"id":3}';
const rows = [...CBOR.parseSeq(jsonl)];
// [{ id: 1 }, { id: 2 }, { id: 3 }]Normalize CDN
import { CBOR } from '@cbortech/cbor';
const text = CBOR.format('{ "b" : [ 1,2 ], "a" : true }', { indent: 2 });
console.log(text);
// {
// "b": [
// 1,
// 2
// ],
// "a": true
// }Keep leaf containers on one line
inlineLeafContainers keeps a container on a single line when none of its
entries contains an array or map (even wrapped in a tag) and every entry
serializes without a line break. Nested leaf containers still collapse
individually, so matrix-like data stays readable. It is applied when
indent is specified.
import { CBOR } from '@cbortech/cbor';
const text = CBOR.format('{"m": [[1,2],[3,4]], "s": (_ "a", "b")}', {
indent: 2,
inlineLeafContainers: true,
});
console.log(text);
// {
// "m": [
// [1, 2],
// [3, 4]
// ],
// "s": (_ "a", "b")
// }Split text strings while formatting
splitNewline splits long text strings at newline characters using CDN
string concatenation. It is applied when indent is specified.
import { CBOR } from '@cbortech/cbor';
const text = CBOR.format('{"text": "line1\\nline2\\nline3"}', {
indent: 2,
splitNewline: true,
});
console.log(text);
// {
// "text": "line1\n" +
// "line2\n" +
// "line3"
// }For strings that contain CDN or JSON-like content, splitCdn formats the
string content with structure-aware line breaks and indentation, the same
way the surrounding CDN is formatted. Both options can be combined, and they
replace the deprecated array-valued textStringFormat option.
import { CBOR } from '@cbortech/cbor';
const text = CBOR.format('{"cdn": "[1,2,3]"}', {
indent: 2,
splitCdn: true,
});
console.log(text);
// {
// "cdn": "[" +
// "1," +
// "2," +
// "3" +
// "]"
// }Preserve raw text strings
By default, CBOR.format() converts raw backtick string literals
(`...`, ``...``, …) to double-quoted form. preserveRawString
re-emits them using their original source text instead. Preserved raw
strings are emitted verbatim: they are never re-escaped, re-indented, or
split by splitCdn / splitNewline. (Raw byte string forms such as
h`...` are covered by preserveByteString.)
import { CBOR } from '@cbortech/cbor';
CBOR.format('`\\d+`');
// '"\\\\d+"'
CBOR.format('`\\d+`', { preserveRawString: true });
// '`\\d+`'Preserve number literal spelling
By default, CBOR.format() normalizes integer and floating-point literals:
hex/octal/binary integers (0xff, 0o377, 0b101) become decimal, trailing
zeros and redundant encoding-indicator suffixes (1.50, 1.5_1) are
dropped. preserveNumberFormat re-emits these literals using their original
CDN source spelling instead, taking precedence over intFormat /
floatFormat. It only affects literals parsed from CDN text — values built
with CBOR.from() or decoded from CBOR bytes always use normal formatting.
import { CBOR } from '@cbortech/cbor';
CBOR.format('{"a": 0xff, "b": 1.50}');
// '{"a":255,"b":1.5}'
CBOR.format('{"a": 0xff, "b": 1.50}', { preserveNumberFormat: true });
// '{"a":0xff,"b":1.50}'Preserve + string concatenation
Note: + string concatenation was removed in draft-26. This section is for
handling legacy syntax.
By default, CBOR.format() joins + string concatenation into a single
literal. preserveConcatenation keeps the original part boundaries for both
text strings and byte strings; add preserveByteString to also keep the
original spelling of byte string parts. Like the split options, it only
takes effect when indent enables pretty-printing — single-line output
always joins the parts.
preserveConcatenation interacts with the split options: splitCdn takes
precedence when the string content parses as CDN, while splitNewline
combines with it by further splitting the preserved parts at newline
characters.
import { CBOR } from '@cbortech/cbor';
CBOR.format('"a" + "b"');
// '"ab"'
CBOR.format('"a" + "b"', { indent: 2, preserveConcatenation: true });
// "a" +
// "b"
CBOR.format("h'68' + b64'aQ'", {
indent: 2,
preserveConcatenation: true,
preserveByteString: true,
});
// h'68' +
// b64'aQ'To render the preserved concatenation using draft-27's t1<<...>> /
b1<<...>> notation instead of +, see modernConcat in
String Concatenation and Indefinite-Length Strings.
Preserve app-string/-sequence notation
Some built-in extensions (dt/DT, ip/IP) support prefix'...'
(app-string), prefix`...` (backtick app-string),
prefix<<...>> (app-sequence), and a raw tag literal (N(...))
notation for the same value, and by default regenerate prefix'...' from
the resolved value on every CBOR.format() call — so
DT`1969-07-21T02:56:16Z`, DT<<'1969-07-21T02:56:16Z'>>, and even the
raw tag form 1(1749772800) all normalize to DT'...' notation, and a
non-canonical DT'...' spelling (e.g. a +00:00 offset instead of Z)
gets rewritten too. preserveAppPrefix keeps the original spelling
instead — whichever form was used. It has no effect when
appPrefix: false is also set (raw tag notation is used either way
regardless of the original spelling), or on values not parsed from one of
these forms.
import { CBOR } from '@cbortech/cbor';
CBOR.format('1(1749772800)');
// "DT'2025-06-13T00:00:00Z'"
CBOR.format('1(1749772800)', { preserveAppPrefix: true });
// "1(1749772800)"
CBOR.format("DT<<'1969-07-21T02:56:16Z'>>", { preserveAppPrefix: true });
// "DT<<'1969-07-21T02:56:16Z'>>"
CBOR.format('DT`1969-07-21T02:56:16Z`', { preserveAppPrefix: true });
// "DT`1969-07-21T02:56:16Z`"Preserve comments
By default, CBOR.fromCDN() discards comments and CBOR.format() emits
none. preserveComments: true captures them while parsing and re-emits each
comment verbatim, with whichever marker (#, //, /* */, / /) it was
originally written with.
import { CBOR } from '@cbortech/cbor';
const text = '{ "a": 1 } # trailing comment';
CBOR.format(text, { indent: 2 });
// '{\n "a": 1\n}'
CBOR.format(text, { indent: 2, preserveComments: true });
// '{\n "a": 1\n} # trailing comment'To normalize every comment's marker instead of keeping the mix as originally
written, use comments — 'c-style' for // and /* */, or
'cdn-style' for # and / /. It has no effect when preserveComments is
true (verbatim wins); explicitly set comments: 'strip' (or leave
both options unset) to drop comments entirely.
CBOR.format(text, { indent: 2, comments: 'c-style' });
// '{\n "a": 1\n} // trailing comment'Only effective when indent enables pretty-printing: single-line output
strips all comments regardless, since line comments can only be terminated
by a newline.
Preserve blank lines
By default, CBOR.format() drops blank lines between array/map entries (and
(_ ...) chunks) when re-serializing. preserveBlankLines re-emits a single
blank line above an entry that had one anywhere before it in the source, so
paragraph-like groupings of entries survive a reformat — at most one blank
line per gap, regardless of how many were originally there. Detection is
based on entry positions alone: it does not require preserveComments and is
unaffected by whether comments are emitted. Only effective when indent
enables pretty-printing; a container with a preserved blank line is always
rendered one entry per line, even under inlineLeafContainers. Included in
preserveAll.
import { CBOR } from '@cbortech/cbor';
const src = `[
1,
2,
3
]`;
CBOR.format(src, { indent: 2 });
// [
// 1,
// 2,
// 3
// ]
CBOR.format(src, { indent: 2, preserveBlankLines: true });
// [
// 1,
// 2,
//
// 3
// ]Format with minimal changes
preserveAll turns on every preserve* option at once, to reformat CDN
text — e.g. when reformatting on save in an editor — touching only
whitespace/indentation and leaving most literals' original spelling
untouched (bignums are the one exception; see preserveNumberFormat
above). An explicitly-set individual option (including false) still wins
over preserveAll.
import { CBOR } from '@cbortech/cbor';
CBOR.format('{"a":0xff,"b":1.5_1,"c":b64\'aGk=\'}', {
indent: 2,
preserveAll: true,
});
// {
// "a": 0xff,
// "b": 1.5_1,
// "c": b64'aGk='
// }CBOR.format() passes the same options to both fromCDN() and toCDN()
internally, so this one option is enough. Calling them separately needs
preserveAll (or preserveComments) on the fromCDN() side too, since
comments must be captured while parsing to be re-emittable later:
const item = CBOR.fromCDN(text, { preserveAll: true });
item.toCDN({ preserveAll: true, indent: 2 });Validate CBOR / CDN / hex dump
validate checks input for well-formedness and validity without throwing.
Recoverable violations (e.g. duplicate map keys) are collected into
warnings instead of stopping decoding; truly malformed data is reported via
error instead (for CDN syntax errors, a CdnSyntaxError with its position
fields intact). Informational hints — e.g. an app-string prefix that matches
a known optional extension which isn't registered — never affect valid and
are collected separately into hints. type selects the input format:
'cbor' (default), 'cdn', or 'hex'.
import { CBOR } from '@cbortech/cbor';
// CBOR bytes — duplicate map key "a" is a recoverable violation
CBOR.validate(new Uint8Array([0xa2, 0x61, 0x61, 0x01, 0x61, 0x61, 0x02]), {
type: 'cbor',
});
// { valid: false, count: 1, warnings: [{ message: 'duplicate map key at offset 4', offset: 4 }], hints: [] }
// CDN text — well-formed input
CBOR.validate('{"a": 1}', { type: 'cdn' });
// { valid: true, count: 1, warnings: [], hints: [] }
// Annotated hex dump text — truncated array (length 3, only 2 elements present)
CBOR.validate('83 -- Array of length 3\n 01 -- 1\n 02 -- 2', {
type: 'hex',
});
// { valid: false, count: 0, warnings: [], hints: [], error: Error(...) }Working With The AST
CBOR.fromCBOR(), CBOR.fromCDN(), and CBOR.fromJS() return a CBOR item.
Concrete node classes such as CborTextString, CborByteString, CborArray,
and CborTag are exported from @cbortech/cbor/ast for extensions. Every item
supports these methods:
import { CBOR } from '@cbortech/cbor';
import { CborItem } from '@cbortech/cbor/ast';
const item = CBOR.fromCDN('{ "x": 1 }');
item satisfies CborItem;
const bytes = item.toCBOR();
const text = item.toCDN();
const value = item.toJS();Parse to AST, then serialize
import { CBOR } from '@cbortech/cbor';
const item = CBOR.fromCDN('[_ 1, 2, 3]');
console.log(item.toCDN());
// [_ 1,2,3]
console.log(item.toCBOR());
// Uint8Array(...)Decode to AST, then inspect as CDN
import { CBOR } from '@cbortech/cbor';
const item = CBOR.fromCBOR(new Uint8Array([0x83, 0x01, 0x02, 0x03]));
console.log(item.toCDN());
// [1,2,3]
console.log(item.toJS());
// [1, 2, 3]JSON-like API
CBOR.parse() and CBOR.stringify() intentionally feel similar to
JSON.parse() and JSON.stringify().
Unlike JSON, CBOR can represent undefined as a value. Use CBOR.OMIT from a
reviver or replacer when you want to remove an object entry or map entry
explicitly, instead of producing an undefined value.
Reviver function
import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse(
'{"createdAt": "2026-05-06T00:00:00Z"}',
(key, value) => {
if (key === 'createdAt') return new Date(value);
return value;
}
);
console.log(value);
// { createdAt: 2026-05-06T00:00:00.000Z }Replacer function
import { CBOR } from '@cbortech/cbor';
const text = CBOR.stringify({ id: 1, password: 'secret' }, (key, value) =>
key === 'password' ? CBOR.OMIT : value
);
console.log(text);
// {"id":1}Replacer key list
import { CBOR } from '@cbortech/cbor';
const text = CBOR.stringify(
{ id: 1, name: 'Alice', password: 'secret' },
['id', 'name'],
2
);
console.log(text);
// {
// "id": 1,
// "name": "Alice"
// }Default Options
Create a CBOR instance when you want to reuse the same options.
import { CBOR } from '@cbortech/cbor';
const cbor = new CBOR({
extensions: [CBOR.dt_as_Date],
indent: 2,
});
const value = cbor.parse("DT'2026-05-06T00:00:00Z'");
console.log(value);
// Date(...)
console.log(cbor.stringify({ value }));
// {
// "value": DT'2026-05-06T00:00:00Z'
// }Dates
CDN dt'...' and DT'...' literals are parsed by default. Add CBOR.dt_as_Date
when you want JavaScript Date objects.
import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse("DT'2026-05-06T00:00:00Z'", {
extensions: [CBOR.dt_as_Date],
});
console.log(value instanceof Date);
// trueimport { CBOR } from '@cbortech/cbor';
const text = CBOR.stringify(new Date('2026-05-06T00:00:00Z'), {
extensions: [CBOR.dt_as_Date],
});
console.log(text);
// DT'2026-05-06T00:00:00Z'Per-item option overrides
itemOptions on toJS() is called for every node before it is converted, so
one part of a document can be converted differently from the rest. Return a
partial options object to override options for that node and its descendants,
or undefined to leave them unchanged. ctx.path identifies the node as a
sequence of array indices and map keys, empty at the root.
It can be called more than once for the same node — when a reviver is also
present, arrays and object-mode maps convert each child at least twice: once
to build a value visible to an earlier sibling's own reviver call, and once
more for the value that's actually kept. Write it as a pure function of
node/ctx, not relying on how many times it runs.
import { CBOR } from '@cbortech/cbor';
const item = CBOR.fromCDN(
`{"date1": DT'2026-08-23T00:00:00Z', "date2": DT'2026-08-23T00:00:00Z'}`
);
const value = item.toJS({
stripTags: true,
itemOptions: (_node, ctx) =>
ctx.path.length === 1 && ctx.path[0] === 'date1'
? { extensions: [CBOR.dt_as_Date] }
: undefined,
});
console.log(value);
// { date1: Date(...), date2: 1787443200 }extensions on ToJSOptions also works on its own, without itemOptions, to
reinterpret an entire tree: a value parsed with the default dt extension can
still be converted with dt_as_Date (or vice versa) by passing extensions
directly to toJS().
ctx.options carries the options already in effect for the node — the root
options merged with whatever an ancestor's itemOptions already returned —
so a callback can build on the current value of an option instead of
overriding it outright:
itemOptions: (_node, ctx) => ({
extensions: [...(ctx.options.extensions ?? []), CBOR.dt_as_Date],
});toCDN() takes the same itemOptions option, formatting one part of a
document differently from the rest:
import { CBOR } from '@cbortech/cbor';
const item = CBOR.fromCDN('{"raw": 255, "count": 255}');
const text = item.toCDN({
itemOptions: (_node, ctx) =>
ctx.path.length === 1 && ctx.path[0] === 'raw'
? { intFormat: 'hex' }
: undefined,
});
console.log(text);
// {"raw":0xff,"count":255}There is no reviver for toCDN(), so it can't be called more than once for
that reason — but it can still be called more than once for a node that's
also a toCDN() layout decision (inlineLeafContainers's one-line collapse
check, or a tag/app-sequence value's own multi-word check re-render an entry
purely to answer that question before the real render). Write it as a pure
function here too.
String Concatenation and Indefinite-Length Strings
The t1 / b1 / ilbs / ilts app-extensions from
draft-ietf-cbor-edn-literals-27 (§3.5 / §3.6) are enabled by default.
t1<<...>> and b1<<...>> join (text or byte) string arguments from left to
right into a single text string (t1) or byte string (b1). Arguments may
also be ellipses (...) to elide parts of a string.
import { CBOR } from '@cbortech/cbor';
const text = CBOR.fromCDN('t1<<"Hello ", "world">>');
console.log(text.toCDN({ appPrefix: false }));
// "Hello world"
const bytes = CBOR.fromCDN("b1<<'Hello ', h'776f726c64'>>");
console.log(bytes.toCDN({ appPrefix: false }));
// 'Hello world'ilbs<<...>> / ilts<<...>> build an indefinite-length byte / text string
with one chunk per argument, honoring encoding indicators on each argument.
They replace the deprecated (_ chunk, ...) streamstring syntax for new CDN
documents; this library keeps accepting the legacy syntax on input.
import { CBOR } from '@cbortech/cbor';
const v = CBOR.fromCDN("ilbs<<'Hello ', 'world'>>");
console.log(v.toCDN({ appPrefix: false }));
// (_ 'Hello ','world')Emitting t1/b1/ilbs/ilts notation
Parsing already accepts t1/b1/ilbs/ilts notation, but by default
toCDN()/CBOR.format() never emit it on their own: a preserved
concatenation (preserveConcatenation) still renders as +, and an
indefinite-length string still renders as the legacy (_ ...) streamstring
form. modernConcat and modernStreamSyntax opt into emitting the draft-27
notation instead — both default to false (the legacy syntax), and both fall
back to it when appPrefix is false.
import { CBOR } from '@cbortech/cbor';
CBOR.format('"a" + "b"', {
indent: 2,
preserveConcatenation: true,
modernConcat: true,
});
// t1<<"a", "b">>
CBOR.format('(_ "a", "b")', { modernStreamSyntax: true });
// ilts<<"a","b">>modernConcat also applies within a ... elision chain (§5.2), rendering
"a" + ... + "b" as t1<<"a", ..., "b">> — unlike plain concatenation, this
happens regardless of preserveConcatenation, since an elision chain has no
single-literal collapsed form to fall back to in the first place.
[!NOTE] Neither option converts
t1/b1/ilbs/iltssource back to the legacy notation: a value parsed fromt1<<...>>(orilbs<<...>>, etc.) keeps that exact spelling on output regardless ofmodernConcat/modernStreamSyntax— as long asappPrefixis notfalse,encodingIndicatorsis'auto'(both defaults), and the source is either single-line or being rendered withindentenabled.encodingIndicators: 'always'/'never'orappPrefix: falsestill normalize it like any other app-string value (e.g.t1<<"a", "b">>becomes"ab"_iunderencodingIndicators: 'always'), and a multi-line source falls back to normalized output in single-line mode (that layout can't be reproduced withoutindent):CBOR.format('t1<<\n "a",\n "b"\n>>'); // '"ab"' — falls back: multi-line source, no `indent` CBOR.format('t1<<\n "a",\n "b"\n>>', { indent: 2 }); // 't1<<\n "a",\n "b"\n>>' — kept verbatimBoth options only affect how a value reconstructed from a
+chain or a(_ ...)chunk list is newly rendered.
[!NOTE] The identifiers
t1andb1are explicitly provisional in draft-27 and may be renamed by the CBOR working group.
float
Interprets a hex bit-pattern as an IEEE 754 floating-point value (draft-ietf-cbor-edn-literals-27 §3.8). Enabled by default.
import { CBOR } from '@cbortech/cbor';
const v = CBOR.fromCDN("float'7e00'");
console.log(v.toCDN({ appPrefix: false }));
// NaN
// Interpret bytes as float bits
const v2 = CBOR.fromCDN("float<<h'3f800000'>>");
console.log(v2.toCDN({ appPrefix: false }));
// 1.0_2Optional Extensions
This package includes several bundled extensions that are not enabled by
default. Import what you need and pass it through the extensions option.
b32 / h32
Byte-string literals using RFC 4648 Base32 encoding. These prefixes are described in §8 of RFC 8949 and also mentioned in draft-ietf-cbor-edn-literals.
b32— §6 Base32 (A–Z 2–7alphabet)h32— §7 Base32Hex (0–9 A–Valphabet)
import { CBOR, b32, h32 } from '@cbortech/cbor';
const v1 = CBOR.fromCDN("b32'AEBAGBA'", { extensions: [b32] });
console.log(v1.toCDN({ appPrefix: false }));
// h'01020304'
const v2 = CBOR.fromCDN("h32'00P00'", { extensions: [h32] });
console.log(v2.toCDN({ appPrefix: false }));
// h'003200'same
same<<expr, expr, ...>> verifies that every item in the sequence encodes to
identical CBOR bytes and returns the first item. This extension is described in
draft-bormann-cbor-edn-app-ext.
import { CBOR, same } from '@cbortech/cbor';
const v = CBOR.fromCDN("same<<h'0102', h'0102'>>", { extensions: [same] });
console.log(v.toCDN({ appPrefix: false }));
// h'0102'
// A single-item sequence always passes
const v2 = CBOR.fromCDN('same<<42>>', { extensions: [same] });
console.log(v2.toCDN({ appPrefix: false }));
// 42Additional app-extensions are published as separate packages. Install
the ones you need and pass them through the extensions option.
hash
hash is an app-extension defined in §3.4 of
draft-ietf-cbor-edn-literals.
It represents cryptographic hash values in the form hash'algorithm:value'.
Because it requires an external cryptographic library, it is provided separately
as @cbortech/hash-extension.
npm install @cbortech/hash-extensionimport { CBOR } from '@cbortech/cbor';
import { hash } from '@cbortech/hash-extension';
const cbor = new CBOR({ extensions: [hash] });
const digest = cbor.parse(
"hash'sha-256:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"
);
// Uint8Array(32) [227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200,
// 153, 111, 185, 36, 39, 174, 65, 228, 100, 155, 147, 76,
// 164, 149, 153, 27, 120, 82, 184, 85]uuid
uuid is a library-specific app-extension, provided separately as
@cbortech/uuid-extension.
npm install @cbortech/uuid-extensionimport { CBOR } from '@cbortech/cbor';
import { uuid } from '@cbortech/uuid-extension';
const cbor = new CBOR({ extensions: [uuid] });
const id = cbor.parse("uuid'550e8400-e29b-41d4-a716-446655440000'");
// Uint8Array(16) [85, 14, 132, 0, 226, 155, 65, 212, 167, 22, 68, 102, 85, 68, 0, 0]set / map
SET and MAP are library-specific app-extensions for tagged Set and
Map values. They are provided together as
@cbortech/set-map-extensions.
SET<<[...]>> produces CBOR tag 258 over an array, and MAP<<{...}>> produces
CBOR tag 259 over a map.
npm install @cbortech/set-map-extensionsimport { CBOR } from '@cbortech/cbor';
import { set, map } from '@cbortech/set-map-extensions';
const cbor = new CBOR({ extensions: [set, map] });
const roles = cbor.parse('SET<<["admin", "editor"]>>');
// Set { 'admin', 'editor' }
const scores = cbor.parse('MAP<<{"alice": 98, "bob": 72}>>');
// Map { 'alice' => 98, 'bob' => 72 }Tags
Use CBOR.Tag for CBOR tagged values in JavaScript.
import { CBOR } from '@cbortech/cbor';
const tagged = CBOR.Tag.set('hello', 42n);
const text = CBOR.stringify(tagged);
console.log(text);
// 42("hello")import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse('42("hello")');
console.log(CBOR.Tag.get(value));
// 42n
console.log(CBOR.Tag.getValue(value));
// "hello"Use stripTags: true when you only need the tagged content as a plain
JavaScript value.
import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse('42("hello")', { stripTags: true });
console.log(value);
// "hello"Simple Values
Use CBOR.Simple for CBOR simple values other than false, true, null, and
undefined.
import { CBOR } from '@cbortech/cbor';
const text = CBOR.stringify(new CBOR.Simple(16));
console.log(text);
// simple(16)import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse('simple(16)');
console.log(value instanceof CBOR.Simple);
// true
console.log(value.value);
// 16Maps
By default, CBOR maps with text keys become plain JavaScript objects.
import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse('{"a": 1, "b": 2}');
console.log(value);
// { a: 1, b: 2 }Use mapAs: 'entries' when you need to preserve non-string keys or duplicate
keys.
import { CBOR } from '@cbortech/cbor';
const entries = CBOR.parse('{1: "one", 1: "uno"}', {
mapAs: 'entries',
});
console.log(entries instanceof CBOR.MapEntries);
// true
console.log(entries);
// [[1, "one"], [1, "uno"]]CBOR.MapEntries can be passed back to CBOR.stringify() or CBOR.encode().
import { CBOR } from '@cbortech/cbor';
const entries = new CBOR.MapEntries([1, 'one'], [1, 'uno']);
console.log(CBOR.stringify(entries));
// {1:"one",1:"uno"}Hex Dumps
CBOR.toHex() and CBOR.fromHex() are the shortcut entry points (see Quick Examples).
For full AST access — byte ranges, re-encoding, selective inspection — use item.toHexDump() and CBOR.fromHexDump() directly:
import { CBOR } from '@cbortech/cbor';
const item = CBOR.fromCDN('[_ 1, [2, 3]]');
const dump = item.toHexDump();
console.log(dump);
// 9F -- Start indefinite-length array
// ...import { CBOR } from '@cbortech/cbor';
const item = CBOR.fromHexDump(`
83 -- Array of length 3
01 -- 1
02 -- 2
03 -- 3
`);
console.log(item.toCDN());
// [1,2,3]Tokenization
The @cbortech/cbor/cdn subpath exposes the same lexer the parser uses, for
tooling such as syntax highlighters that must stay in exact agreement with
parsing behavior:
import { tokenize, tokenizeLenient } from '@cbortech/cbor/cdn';
const { tokens, comments } = tokenize('[1, "ab"] # note');
// tokens: [{ type: 'LBRACKET', offset: 0, endOffset: 1, ... }, ...]
const lenient = tokenizeLenient('[1, "ab');
// Never throws: clean tokens, then one ERROR token covering the
// unscannable tail, plus the failure in lenient.error.Syntax errors thrown by fromCDN/parse/tokenize are CdnSyntaxError
instances (a SyntaxError subclass, also exported from the main entry) and
carry offset, line, column, and — where known — endOffset.
CDDL
The @cbortech/cbor/cddl subpath contains a parser, compiler, and validator for
CDDL, the schema language for describing CBOR data structures. A compiled schema
validates CBOR bytes, CDN text, or a CborItem AST:
import { CDDL } from '@cbortech/cbor/cddl';
const schema = CDDL.compile('point = { x: int, y: int }');
console.log(schema.validate('{"x": 12, "y": -3}').valid);
// trueValidation returns a result object rather than throwing. Failures include the
instance path and source offsets for both the input and schema. Validation uses
the first rule by default; pass rule to select another non-generic type rule.
Options also include features, maxDepth, and maxSteps.
All control operators from
RFC 8610 are implemented, along with
RFC 9165's .plus, .cat, and
.feature. Enable .feature names with the features validation option.
Unsupported operators such as .abnf are reported in result.warnings and
matched without their constraint.
The main CBOR facade also accepts a compiled schema or CDDL source text through
the cddl option:
import { CBOR } from '@cbortech/cbor';
const value = CBOR.parse('{"x": 12, "y": -3}', {
cddl: 'point = { x: int, y: int }',
});
// { x: 12, y: -3 }Throwing methods such as parse, decode, and encode throw
CddlMismatchError on a mismatch; CBOR.validate() instead collects failures
in result.cddlErrors. Pass validator options through cddlValidationOptions,
or set cddl as an instance default with new CBOR({ cddl: … }).
CDDL.compile() throws CddlSyntaxError or CddlSemanticError; use
{ strict: false } to collect semantic issues in schema.warnings instead.
Compiled schemas can be formatted with schema.format(). The subpath also
exports tokenize, tokenizeLenient, and a typed rule AST through schema.ast
and schema.rules.
Public API
The documented public exports are:
CBORCdnSyntaxErrorCddlMismatchError(thrown by thecddloption; see CDDL)
The CBOR facade also exposes:
CBOR.TagCBOR.SimpleCBOR.MapEntriesCBOR.dt_as_DateCBOR.OMIT
Lower-level CDN tokenization lives in @cbortech/cbor/cdn
(tokenize, tokenizeLenient, Token, TokenType, EdnComment),
and AST node classes in @cbortech/cbor/ast.
The CDDL compiler lives in @cbortech/cbor/cddl
(CDDL, CddlSchema, CddlSyntaxError, CddlSemanticError,
CddlMismatchError, tokenize, tokenizeLenient, and the CDDL AST
types).
Specifications
- CBOR
- CBOR Sequences
- CDN (CBOR-EDN)
- CDDL
Implementation notes:
- CDN follows draft-27 while retaining draft-25's
(_ ...)streamstring syntax and+string-concatenation syntax. - CDDL implements every RFC 8610 control operator, plus RFC 9165's
.plus,.cat, and.feature. - The RFC 9682 updates are implemented: its string-literal grammar (including
\u{...}), empty data models at the syntax layer (a model with no rules is still a semantic error when compiled), and non-literal#6.<type>/#7.<type>head numbers. CommentPCHARvalidation, bare CR line endings, and comments ending at EOF are intentionally accepted more leniently than the collected ABNF.
License
Apache-2.0
