Tiny CBOR Schema
A runtime schema builder for CBOR that provides encoding and decoding between CBOR and TypeScript types. It pairs with Tiny CBOR under the hood and exposes a concise API via cs
. Type inference with valueOf<T>
keeps your application strongly typed.
Install
npm i @levischuck/tiny-cbor-schema
pnpm add @levischuck/tiny-cbor-schema
deno add jsr:@levischuck/tiny-cbor-schema
bunx jsr add @levischuck/tiny-cbor-schema
Quick start
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
// COSE EC2 key parameters (aligned with common WebAuthn usage) const Ec2KeyParametersSchema = cs.map([ cs.numberField(1, 'kty', cs.literal(2)), // Key Type: EC2 cs.numberField(2, 'kid', cs.optional(cs.bytes)), cs.numberField(3, 'alg', cs.optional(cs.literal(-7))), // ES256 cs.numberField( 4, 'key_ops', cs.optional(cs.array(cs.union([cs.literal(1), cs.literal(2)]))), ), cs.numberField(-1, 'crv', cs.literal(1)), // P-256 cs.numberField(-2, 'x', cs.bytes), cs.numberField(-3, 'y', cs.union([cs.bytes, cs.boolean])), cs.numberField(-4, 'd', cs.optional(cs.bytes)), ]); type Ec2KeyParameters = valueOf<typeof Ec2KeyParametersSchema> const publicKey: Ec2KeyParameters = { kty: 2, alg: -7, crv: 1, x: new Uint8Array([1,2,3]), y: new Uint8Array([4,5,6]), }; console.log('publicKey:', publicKey); const encoded = cs.toCBOR(Ec2KeyParametersSchema, publicKey); console.log('encoded:', encoded); const decoded = cs.fromCBOR(Ec2KeyParametersSchema, encoded); // typed as Ec2KeyParameters console.log('decoded:', decoded);
Primitives
import { cs } from '@levischuck/tiny-cbor-schema'
// Round trip a few values without aliasing primitives const encString = cs.toCBOR(cs.string, 'hello'); console.log('encString:', encString); const decString = cs.fromCBOR(cs.string, encString); // 'hello' console.log('decString:', decString); const encBig = cs.toCBOR(cs.bigint, BigInt('9007199254740992')); console.log('encBig:', encBig); const decBig = cs.fromCBOR(cs.bigint, encBig); console.log('decBig:', decBig); const xCoord = new Uint8Array([1,2,3]); console.log('xCoord:', xCoord); const encBytes = cs.toCBOR(cs.bytes, xCoord); console.log('encBytes:', encBytes); const decBytes = cs.fromCBOR(cs.bytes, encBytes); console.log('decBytes:', decBytes);
Arrays and tuples
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
const numbers = cs.array(cs.integer); type Numbers = valueOf<typeof numbers>; // number[] const encoded = cs.toCBOR(numbers, [1,2,3]); console.log('encoded:', encoded); const decoded = cs.fromCBOR(numbers, encoded); // [1,2,3] console.log('decoded:', decoded); const pointSchema = cs.tuple([cs.float, cs.float]); type Point = valueOf<typeof pointSchema>; // [number, number] const p = cs.fromCBOR(pointSchema, cs.toCBOR(pointSchema, [10.5, 20.7])); console.log('p:', p);
Maps and fields
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
const cwtClaims = cs.map([ cs.numberField(1, 'iss', cs.string), // issuer (numeric key) cs.numberField(4, 'exp', cs.integer), // expiration time cs.numberField(7, 'cti', cs.bytes), // CWT ID cs.field('cty', cs.optional(cs.string)), // string-keyed field ]); type CWT = valueOf<typeof cwtClaims>; const claims: CWT = { iss: 'example-issuer', exp: 1_725_000_000, cti: new Uint8Array([1,2,3]), }; console.log('claims:', claims); const encoded = cs.toCBOR(cwtClaims, claims); console.log('encoded:', encoded); const decoded = cs.fromCBOR(cwtClaims, encoded) as CWT; console.log('decoded:', decoded);
Optional and literal values
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
const maybeNumber = cs.optional(cs.float); type MaybeNumber = valueOf<typeof maybeNumber>; // number | undefined const status = cs.literal('ok'); type Status = valueOf<typeof status>; // 'ok' const enc1 = cs.toCBOR(maybeNumber, 42); console.log('enc1:', enc1); const dec1 = cs.fromCBOR(maybeNumber, enc1); // 42 console.log('dec1:', dec1); const enc2 = cs.toCBOR(maybeNumber, undefined); console.log('enc2:', enc2); const dec2 = cs.fromCBOR(maybeNumber, enc2); // undefined console.log('dec2:', dec2); const enc3 = cs.toCBOR(status, 'ok'); console.log('enc3:', enc3); const dec3 = cs.fromCBOR(status, enc3); // 'ok' console.log('dec3:', dec3);
Unions
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
const numberOrString = cs.union([cs.float, cs.string]); type NumberOrString = valueOf<typeof numberOrString>; // number | string const encA = cs.toCBOR(numberOrString, 42); console.log('encA:', encA); const decA = cs.fromCBOR(numberOrString, encA); // 42 console.log('decA:', decA); const encB = cs.toCBOR(numberOrString, 'hello'); console.log('encB:', encB); const decB = cs.fromCBOR(numberOrString, encB); // 'hello' console.log('decB:', decB);
Tagged values
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
// Tag 0 is standard datetime string in CBOR registry const dateTime = cs.tagged(0, cs.string); type DateTime = valueOf<typeof dateTime>; // { tag: 0, value: string } const now: DateTime = { tag: 0, value: new Date().toISOString() }; console.log('now:', now); const enc = cs.toCBOR(dateTime, now); console.log('enc:', enc); const dec = cs.fromCBOR(dateTime, enc); // preserves tag and value console.log('dec:', dec);
Nested CBOR
import { cs } from '@levischuck/tiny-cbor-schema'
const meta = cs.map([ cs.field('version', cs.integer), ]); const document = cs.map([ cs.field('content', cs.string), cs.field('metadata', cs.nested(meta)), ]); const encoded = cs.toCBOR(document, { content: 'Hello', metadata: { version: 1 } }); console.log('encoded:', encoded); const decoded = cs.fromCBOR(document, encoded); console.log('decoded:', decoded);
Lazy and recursive schemas
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
type Tree = { value: string; children: Tree[] }; let treeSchema: any; treeSchema = cs.map([ cs.field('value', cs.string), cs.field('children', cs.array(cs.lazy(() => treeSchema))) ]); type TreeValue = valueOf<typeof treeSchema>; // Tree const tree: Tree = { value: 'root', children: [{ value: 'leaf', children: [] }] }; console.log('tree:', tree); const enc = cs.toCBOR(treeSchema, tree); console.log('enc:', enc); const dec = cs.fromCBOR(treeSchema, enc); console.log('dec:', dec);
API overview
cs (schema builder)
Main schema builder class containing all schema constructors and primitive types. Prefer the shorthand alias cs
. Primitives: string
, integer
, bigint
, float
, boolean
, bytes
. Constructors: array
, tuple
, union
, tagged
, optional
, map
, field
, numberField
, nested
, literal
, lazy
.
fromCBOR
Decodes a CBOR byte array using the provided schema. Returns the decoded, typed value.
- Parameters:
schema
– schema to use;data
– CBOR encoded data - Returns: Decoded value typed as
valueOf<typeof schema>
toCBOR
Encodes a value to CBOR using the provided schema. Returns a Uint8Array
of encoded bytes.
- Parameters:
schema
– schema to use;value
– typed value to encode - Returns:
Uint8Array
CBOR encoded data
valueOf<T>
Infers the TypeScript type from a tiny CBOR schema object, allowing you to annonate the type of decoded value.Please note that any exported type using valueOf
will be considered a slow type.
COSE and CWT examples
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
const ec2KeyParametersSchema = cs.map([ cs.numberField(1, 'kty', cs.literal(2)), cs.numberField(3, 'alg', cs.literal(-7)), cs.numberField(-1, 'crv', cs.literal(1)), cs.numberField(-2, 'x', cs.bytes), cs.numberField(-3, 'y', cs.bytes), ]); type Ec2KeyParameters = valueOf<typeof ec2KeyParametersSchema>; const key: Ec2KeyParameters = { kty: 2, alg: -7, crv: 1, x: new Uint8Array([/* x coord */]), y: new Uint8Array([/* y coord */]), }; console.log('key:', key); const coseBytes = cs.toCBOR(ec2KeyParametersSchema, key); console.log('coseBytes:', coseBytes); const parsedKey = cs.fromCBOR(ec2KeyParametersSchema, coseBytes); console.log('parsedKey:', parsedKey);
import { cs, type valueOf } from '@levischuck/tiny-cbor-schema'
const cwtClaims = cs.map([ cs.numberField(1, 'iss', cs.string), // Issuer cs.numberField(4, 'exp', cs.integer), // Expiration time cs.numberField(7, 'cti', cs.bytes), // CWT ID ]); type CWT = valueOf<typeof cwtClaims>; const claims: CWT = { iss: 'example-issuer', exp: 1_725_000_000, cti: new Uint8Array([0x01, 0x02, 0x03]), }; console.log('claims:', claims); const cwtBytes = cs.toCBOR(cwtClaims, claims); console.log('cwtBytes:', cwtBytes); const parsedClaims = cs.fromCBOR(cwtClaims, cwtBytes); console.log('parsedClaims:', parsedClaims);