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

@typesugar/codec

v0.1.1

Published

🧊 Versioned codec generation with schema evolution — serde + protobuf for TypeScript

Readme

@typesugar/codec

Versioned codec generation with schema evolution -- serde + protobuf for TypeScript.

The Problem

Serialization formats evolve. Fields get added, removed, renamed. Old data needs to work with new code. Most TypeScript serialization libraries ignore this entirely, leaving you to write migration logic by hand.

@typesugar/codec gives you versioned schemas with automatic migration chain generation, so old data decodes correctly through any number of version bumps.

Installation

npm install @typesugar/codec

Requires @typesugar/core as a peer dependency and the TypeSugar transformer for compile-time macro expansion.

Quick Start

import { schema } from "@typesugar/codec";

interface UserProfile {
  name: string;
  email: string;
  avatar?: string;
  preferences: Record<string, unknown>;
  theme: string;
}

const userCodec = schema<UserProfile>("UserProfile", 3)
  .field("name", "string")
  .field("email", "string", { since: 2, defaultValue: "" })
  .field("avatar", "string", { since: 2, optional: true })
  .field("preferences", "object", { since: 3, defaultValue: {} })
  .field("theme", "string", { since: 3, defaultValue: "light" })
  .field("legacyField", "string", { removed: 3, optional: true })
  .buildCodec();

// Encode at current version (v3)
const encoded = userCodec.encode({
  name: "Alice",
  email: "[email protected]",
  preferences: { lang: "en" },
  theme: "dark",
});

// Decode v1 data -- migrations apply automatically
const old = '{"__v": 1, "name": "Bob"}';
const bob = userCodec.decodeAny(old);
// { name: "Bob", email: "", avatar: null, preferences: {}, theme: "light" }

Schema Evolution Rules

| Annotation | Meaning | Constraint | | ------------------------------- | --------------------------- | ----------------------------------------------- | | since: N | Field added in version N | N <= current version | | removed: N | Field removed in version N | N > since | | renamed: { version, oldName } | Field renamed at version N | version <= current | | defaultValue: V | Default for older versions | Required for non-optional fields added after v1 | | optional: true | Field may be null/undefined | No default needed |

Validation runs at schema build time. Break a rule and buildCodec() throws with a clear error.

Migration Chains

Migrations are generated automatically from field annotations. Each version step:

  1. Renames fields whose renamed.version matches the target version
  2. Adds new fields (from since) with their default values
  3. Removes fields whose removed version matches

For a v1-to-v3 migration, the chain runs v1->v2 then v2->v3. No manual migration functions needed.

JSON Codec

The default format. Embeds a __v field for version detection.

import { createJsonCodec, defineSchema } from "@typesugar/codec";

const schema = defineSchema("Point", {
  version: 1,
  fields: [
    { name: "x", type: "number" },
    { name: "y", type: "number" },
  ],
});

const codec = createJsonCodec<{ x: number; y: number }>(schema);
const json = codec.encode({ x: 10, y: 20 });
// '{"__v":1,"x":10,"y":20}'
  • codec.decode(data) -- strict, rejects version mismatches
  • codec.decodeAny(data) -- applies migration chain from any known version

Binary Codec

For performance-critical paths. Fixed-layout encoding with explicit field offsets.

import { createBinaryCodec, type FieldLayout } from "@typesugar/codec";

const layout: FieldLayout[] = [
  { name: "x", offset: 0, size: 4, type: "float32" },
  { name: "y", offset: 4, size: 4, type: "float32" },
];

const codec = createBinaryCodec<{ x: number; y: number }>(schema, layout);
const bytes = codec.encode({ x: 1.5, y: 2.5 }); // Uint8Array

Binary format: 4-byte header (2 bytes magic 0x54 0x53, 2 bytes version), then fields at their specified offsets.

Supported field types: uint8, uint16, uint32, int8, int16, int32, float32, float64, string, bytes.

Schema Builder vs. Raw API

The schema() builder validates on .build() / .buildCodec(). For programmatic use, the lower-level functions are available:

import { defineSchema, validateSchema, generateMigrations, fieldsAtVersion } from "@typesugar/codec";

const s = defineSchema("Foo", { version: 2, fields: [...] });
const errors = validateSchema(s);          // SchemaValidationError[]
const history = generateMigrations(s);     // VersionHistory with migration chain
const v1Fields = fieldsAtVersion(s, 1);    // fields active at v1

Zero-Cost Guarantee

Schema definitions are compile-time only -- the @codec macro extracts type structure at build time, so schema metadata is erased from production bundles. The JSON codec has minimal runtime overhead: just property access plus a version field on each encoded object. The binary codec uses fixed-layout DataView reads and writes with no dynamic dispatch. There are no external dependencies beyond @typesugar/core.

Integration

@typesugar/codec works with @typesugar/validate for runtime validation of decoded data -- decode first, then validate against your schema constraints. It is compatible with @typesugar/derive for auto-deriving codec instances from type definitions. Codecs produce and consume plain strings or Uint8Array buffers, so they work with any transport layer: HTTP, WebSocket, file I/O, or message queues.

Comparison

| Feature | @typesugar/codec | protobuf | serde | | ----------------------- | --------------------- | ---------------------- | -------------------------- | | Language | TypeScript | Multi-language | Rust | | Schema evolution | Automatic migrations | Manual field numbering | Manual #[serde(default)] | | Binary format | Fixed-layout | Varint + tag | Format-dependent | | JSON support | Built-in | Via jsonpb | Via serde_json | | Compile-time validation | Phase 2 (implemented) | protoc | Compile-time | | Zero external deps | Yes | protobuf runtime | N/A |

Macro Support (Phase 2)

The @codec decorator supports field-level decorators (@since, @removed, @defaultValue) for schema evolution. See field-decorators.test.ts for full coverage:

@codec({ version: 3 })
interface UserProfile {
  name: string;
  @since(2) email: string;
  @since(3) @defaultValue({}) preferences: object;
  @removed(3) legacyField?: string;
}