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

@rzv/make-brand

v0.2.1

Published

Minimal brand toolkit for Zod

Readme

@rzv/make-brand

Minimal brand toolkit for Zod.

@rzv/make-brand turns a Zod schema into a small toolkit for creating, validating, comparing, and composing branded values. It is useful when two values share the same runtime shape but must stay incompatible in TypeScript, such as UserId and OrderId, or Quantity and Price.

Why

TypeScript is structurally typed, so two string values are interchangeable even if they represent different concepts. Branded types add nominal typing on top of your Zod schema while keeping the original runtime value.

  • Runtime validation comes from Zod
  • Type safety comes from the brand
  • Values stay plain strings, numbers, or objects at runtime

Installation

zod is a peer dependency.

npm install @rzv/make-brand zod
pnpm add @rzv/make-brand zod
bun add @rzv/make-brand zod

The package is ESM-only.

Quick Start

import { z } from "zod";
import { makeBrand } from "@rzv/make-brand";

const UserIdBrand = makeBrand(z.string().uuid(), "UserId");
const OrderIdBrand = makeBrand(z.string().uuid(), "OrderId");

type UserId = z.infer<typeof UserIdBrand.schema>;
type OrderId = z.infer<typeof OrderIdBrand.schema>;

const userId = UserIdBrand.create("550e8400-e29b-41d4-a716-446655440000");
const orderId = OrderIdBrand.create("6ba7b810-9dad-11d1-80b4-00c04fd430c8");

function getUserById(id: UserId) {
  return id;
}

getUserById(userId);
// getUserById(orderId); // Type error

At runtime, userId is still just a string. The brand exists to improve type safety and schema composition.

Core API

const Brand = makeBrand(schema, "BrandName");

makeBrand returns a BrandKit with the following fields:

| Field / method | Description | | --------------------------- | ------------------------------------------------------------------------------------ | | schema | Branded Zod schema you can reuse in larger schemas | | brandName | Runtime brand name | | create(value) | Parses and returns a branded value, throws on invalid input | | safeCreate(value) | Returns a branded value or null | | matches(value) | Runtime type guard based on schema validation | | ensure(value, message?) | Assertion helper, throws Error on invalid input | | toPrimitive(value) | Returns the underlying runtime value | | same(a, b, compareFn?) | Equality check, default is strict equality | | compare(a, b, compareFn?) | Sort helper, default uses < / > | | combine(...brands) | Combines this brand with 1-4 additional brands or an array via intersection | | refineTo(next) | Creates a new brand kit with the same brand name and a new schema | | pipeTo(next) | Pipes the current branded schema into another Zod schema and returns a new brand kit |

Validation Patterns

import { z } from "zod";
import { makeBrand } from "@rzv/make-brand";

const UserIdBrand = makeBrand(z.string().uuid(), "UserId");

const strictUserId = UserIdBrand.create("550e8400-e29b-41d4-a716-446655440000");
const maybeUserId = UserIdBrand.safeCreate("not-a-uuid");
// maybeUserId === null

const input: unknown = "550e8400-e29b-41d4-a716-446655440000";

if (UserIdBrand.matches(input)) {
  // input is narrowed to UserId here
}

UserIdBrand.ensure(input, "Invalid user id");

const rawValue = UserIdBrand.toPrimitive(strictUserId);
// rawValue is the same runtime string without branded typing

Schema Composition

Use schema whenever branded values need to participate in larger Zod objects.

import { z } from "zod";
import { makeBrand } from "@rzv/make-brand";

const UserIdBrand = makeBrand(z.string().uuid(), "UserId");
const QuantityBrand = makeBrand(z.number().int().positive(), "Quantity");

const OrderSchema = z.object({
  id: UserIdBrand.schema,
  quantity: QuantityBrand.schema,
});

const order = OrderSchema.parse({
  id: "550e8400-e29b-41d4-a716-446655440000",
  quantity: 5,
});

Comparing Branded Values

same and compare are useful when you want a package-level convention for equality and sorting.

import { z } from "zod";
import { makeBrand } from "@rzv/make-brand";

const QuantityBrand = makeBrand(z.number().int().positive(), "Quantity");

const a = QuantityBrand.create(5);
const b = QuantityBrand.create(10);
const c = QuantityBrand.create(5);

QuantityBrand.same(a, c); // true
QuantityBrand.compare(a, b); // -1

const sorted = [b, a, c].sort(QuantityBrand.compare);

You can also override the comparison for a single call:

QuantityBrand.compare(a, b, (x, y) => (x > y ? -1 : x < y ? 1 : 0));

refineTo and pipeTo

These two methods look similar, but they behave differently:

  • refineTo(next) keeps the brand name and replaces the schema with next
  • pipeTo(next) keeps the current branded schema as the first stage, then pipes it into next
import { z } from "zod";
import { makeBrand } from "@rzv/make-brand";

const QuantityBrand = makeBrand(z.number().int().positive(), "Quantity");

const PercentageBrand = QuantityBrand.refineTo(z.number().int().min(0).max(100));

const UppercaseNameBrand = makeBrand(z.string().min(1), "DisplayName").pipeTo(
  z.string().transform((value) => value.trim().toUpperCase()),
);

PercentageBrand.create(42);
UppercaseNameBrand.create("  alice  "); // "ALICE"

If you need refineTo to preserve previous constraints, include them again in the new schema explicitly.

combine

Combines multiple brands into a single brand using Zod intersection. The resulting brand name joins all contributor names with &.

import { z } from "zod";
import { makeBrand } from "@rzv/make-brand";

const IntBrand = makeBrand(z.number().int(), "Int");
const PositiveBrand = makeBrand(z.number().positive(), "Positive");

const PositiveInt = IntBrand.combine(PositiveBrand);
// PositiveInt.brandName === "Int&Positive"

PositiveInt.create(5); // 5
PositiveInt.create(-1); // throws ZodError (not positive)
PositiveInt.create(1.5); // throws ZodError (not int)

Up to 4 additional brands as arguments:

const MaxHundred = makeBrand(z.number().max(100), "MaxHundred");

const BoundedPositiveInt = IntBrand.combine(PositiveBrand, MaxHundred);
// BoundedPositiveInt.brandName === "Int&Positive&MaxHundred"

Or pass an array:

const StrictInt = IntBrand.combine([PositiveBrand, MaxHundred]);
// StrictInt.brandName === "Int&Positive&MaxHundred"

The combined schema is usable in z.object:

const Schema = z.object({ value: PositiveInt.schema });
const result = Schema.parse({ value: 42 });

Exported Types

The package also exports the main utility types:

  • BrandKit — the full toolkit type returned by makeBrand
  • BrandSymbol — the brand marker { readonly [z.$brand]: T }
  • BrandedType<T, B>T & BrandSymbol<B>
  • BrandedSchema<TSchema, B> — a Zod schema that produces branded output
  • BrandCombineFn — overloaded function signature for combine
  • ExtractBrandName<B> — extracts the brand name from a BrandKit
  • JoinBrands<T> — joins a tuple of brand names with &

Example:

import type { BrandedType } from "@rzv/make-brand";

type UserId = BrandedType<string, "UserId">;

Development

npm install
npm test
npm run check
npm run build

Release helpers:

npm run release:patch
npm run release:minor
npm run release:major

LLM-Ready Documentation

This repository includes documentation files optimized for LLM consumption:

  • llms.txt — compact index following the llms.txt convention, discoverable by Context7 and other LLM tools
  • llms-full.txt — complete API reference in a single file for direct injection into LLM context

These files are automatically indexed by Context7 and compatible with any tool that follows the llms.txt standard.

License

MIT