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

cddl.js

v1.0.10

Published

[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/cddl.js) [![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/cddl.js) [![Release](https://im

Downloads

2,430

Readme

cddl.js

GitHub npm Release CI

Generates Zod schemas, and the TypeScript types inferred from them, from CDDL.

npm downloads chart, log scale

CDDL (RFC 8610) is the IETF standard for describing CBOR and JSON data structures. JavaScript and TypeScript have a CDDL parser (cddl) and a TypeScript-interface generator (cddl2ts), but an interface has no runtime validator, so the two drift apart as soon as one changes without the other. cddl.js takes a CDDL file and emits one TypeScript module in which every named rule becomes a Zod schema and an inferred type, so the type and the validator come from a single generation step.

It supports a deliberately scoped subset of CDDL, not all of RFC 8610. Anything outside that subset throws an error naming the construct and the rule it appeared in, rather than emitting something wrong.

Installing

npm install cddl.js zod

The package is cddl.js on npm. Generated modules import zod, so the project that uses them needs it installed too.

Usage

Given a CDDL file:

person = {
  name: tstr,
  age: uint,
  ? email: tstr,
  id: bstr .size 4,
}

parse it, emit the module and write it out:

import { writeFileSync } from "node:fs";
import { parse } from "cddl.js/parse";
import { emitModule } from "cddl.js/emitter";

const source = emitModule(parse("person.cddl"));
writeFileSync("person.generated.ts", source);

The generated module is, after a header comment saying it is generated and should not be edited by hand:

import { z } from "zod";

export const personSchema = z.lazy(() => z.object({
  "name": z.string(),
  "age": z.number().int().nonnegative(),
  "email": z.string().optional(),
  "id": z.instanceof(Uint8Array).refine((v) => v.length === 4, { message: "expected exactly 4 bytes" }),
}));

export type Person = z.infer<typeof personSchema>;

Use it like any other Zod schema:

import { personSchema, type Person } from "./person.generated.ts";

const ok: Person = personSchema.parse({ name: "Ada", age: 36, id: new Uint8Array(4) });
console.log(ok);
// { name: 'Ada', age: 36, id: Uint8Array(4) [ 0, 0, 0, 0 ] }

console.log(personSchema.safeParse({ name: "Ada", age: -1, id: new Uint8Array(3) }).success);
// false

Entry points

The package has no root export. Each module is imported by its own subpath, and each resolves under both import and require. The CommonJS build is self-contained and does not rely on require() of ES modules.

| Subpath | Export | Purpose | | --- | --- | --- | | cddl.js/parse | parse(filePath: string): unknown | Parses a CDDL file into an AST. The result is typed unknown on purpose: the AST shape belongs to the vendored parser and is not part of this package's contract. | | cddl.js/emitter | emitModule(parsed: unknown): string | Compiles a parsed AST into the source of one TypeScript module. | | cddl.js/emitter | mergeRules(parsed: unknown): MergedRule[] | Groups the top-level rules by name, merging a socket's /= additions into one entry. emitModule calls it first. | | cddl.js/runtime | cborDecodesAs(schema: z.ZodType): z.ZodType<Uint8Array> | Support code that generated modules import when the CDDL uses .cbor or .cborseq. |

What a generated module contains

Each named rule produces two exports: <name>Schema and <Name>, the camel-cased and Pascal-cased rule name with any leading $ removed ($frame-variant becomes frameVariantSchema and FrameVariant). Every schema is wrapped in z.lazy, so rules can reference each other in any order. A rule that can reach itself through such references carries an explicit z.ZodType annotation, because TypeScript cannot infer a type through a cycle. Every other rule's type is inferred from its own expression.

Supported CDDL

| CDDL | Generated | | --- | --- | | tstr, bstr | z.string(), z.instanceof(Uint8Array) | | int, uint, nint | z.number().int(), with .nonnegative() for uint and .negative() for nint | | bool, nil, null, any | z.boolean(), z.null(), z.null(), z.unknown() | | Literal values | z.literal(...) | | Map { ... } | z.object({ ... }), also as an inline value type | | key: type | A required field, or an optional one with ? | | * keytype => type | The map's catch-all: .catchall(type). One per map. | | ? name => type, where name = <literal> is a rule | A field keyed by the literal's string form, so an integer key 1 becomes "1" | | [* type] | z.array(type) | | [a, b, c] | z.tuple([a, b, c]), preserving each position's own type | | a / b | z.union([a, b]) | | Reference to another rule | z.lazy(() => otherSchema) | | $name = x with $name /= y additions | One schema that is the union of every definition | | .size N | The value's length must equal N exactly | | .regexp "..." | .regex(new RegExp("...")), using the pattern as written with no anchors added | | .cbor rule, .cborseq rule | cborDecodesAs(...), described below |

Anything else throws. That includes other native types such as float, occurrence indicators other than none, ? and * (so + and n*m are rejected), more than one catch-all in a map, any occurrence indicator on a fixed array's members, other control operators, and a rule defined more than once without being a socket.

.cbor and the runtime module

bstr .cbor rule generates a byte-string field whose contents must decode as CBOR and match the named rule. This is how nested payloads, such as the fields of a COSE envelope, are described. The field's value stays the original bytes: cborDecodesAs validates the embedded CBOR without transforming it, so decoding, validating and re-encoding reproduces the identical wire bytes. A caller that wants the inner value decodes it separately.

A module that uses .cbor imports cborDecodesAs from ./runtime.js, a sibling of the generated file, so provide a module of that name next to it:

// runtime.ts
export { cborDecodesAs } from "cddl.js/runtime";

Development

Tool versions are pinned in .tool-versions. Tasks run through Turborepo and have the same names as the scripts in package.json:

pnpm install
pnpm build       # bundles the three entry points, with declarations, and checks them with attw
pnpm test        # unit and round-trip tests
pnpm lint
pnpm typecheck

The parser is a vendored copy of webdriverio/cddl in vendor/cddl, carrying fixes that have not yet been released upstream. The tasks above build it first; run pnpm vendor:build yourself before running a script that imports it directly.

The round-trip test generates schemas from a real-world CDDL specification in test/fixtures, then checks that every recorded conformance vector decodes, validates and re-encodes to its original bytes. The fixtures are described in test/fixtures/README.md.

From a checkout, node src/cli.ts <input.cddl> <output.ts> runs the same parse and emit steps on one file. The command line script is not part of the published package.

Background

cddl.js was built for wire-mesh, which needs a TypeScript implementation of a CDDL-described wire protocol, but it is scoped and named to stand on its own. The decisions behind it, including why it builds on the pure-JavaScript cddl parser's AST with an emitter of its own, are recorded in docs/0001-foundation.md and docs/0002-spike.md.

Contributing

Issues and pull requests are welcome. If you know of CDDL tooling for JavaScript or TypeScript that this README does not mention, please open an issue.

Licence

MIT, see LICENSE. The vendored parser is MIT licensed by WebdriverIO, see vendor/cddl/LICENSE.