createBrandedId(config)

Creates a BrandedId class — a typed wrapper around a string ID that prevents accidentally mixing up different ID types (e.g., passing a UserId where a ProductId is expected).

Signature

function createBrandedId(config: {
  brand: string;
  validate: (value: string) => string;
}): BrandedIdClass

Config

PropertyTypeDescription
brandstringA unique brand string. Used as a TypeScript literal type for branding.
validate(value: string) => stringValidates and optionally sanitizes the raw string. Throw on invalid.

Returned Class

MemberTypeDescription
BrandedId.create(value)BrandedIdStatic factory. Validates and returns instance.
id.valuestring & { __brand }The branded string value.
id.equals(other)booleanValue comparison.
id.toString()stringReturns the raw string.
id.toJSON()stringJSON serialization returns the string.

Example

import { createBrandedId } from "@sotajs/ddd";

const UserId = createBrandedId({
  brand: "UserId",
  validate: (v) => {
    if (!v) throw new Error("UserId cannot be empty");
    return v;
  },
});

const ProductId = createBrandedId({
  brand: "ProductId",
  validate: (v) => v,
});

const uid = UserId.create("abc-123");
const pid = ProductId.create("abc-123");

// uid and pid have the same string value but are incompatible types
console.log(uid.value);     // "abc-123"
console.log(uid.toString()); // "abc-123"
console.log(JSON.stringify(uid)); // '"abc-123"'

Why BrandedId?

Without branded IDs:

function getUser(id: string) { /* ... */ }
function getProduct(id: string) { /* ... */ }

const productId = "abc-123";
getUser(productId); // compiles fine — wrong!

With branded IDs:

function getUser(id: ReturnType<typeof UserId.create>) { /* ... */ }
function getProduct(id: ReturnType<typeof ProductId.create>) { /* ... */ }

const productId = ProductId.create("abc-123");
getUser(productId); // TypeScript error — UserId expected, got ProductId

Sanitization

The validate callback can also sanitize:

const TrimmedId = createBrandedId({
  brand: "Trimmed",
  validate: (v) => v.trim().toLowerCase(),
});

const id = TrimmedId.create("  HELLO  ");
console.log(id.value); // "hello"

Pitfalls

  • Brand is compile-time only. At runtime, the value is a plain string.
  • Not a UUID validator. Write your own validation (regex, UUID check) in the validate callback.
  • TypeScript-only safety. JavaScript consumers won’t get type errors, but the runtime validation still applies.