From harness-claude
Implements TypeScript branded opaque types to prevent mixing semantically distinct primitives like UserId vs PostId and distinguish validated from unvalidated data at type level.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Prevent mixing semantically distinct primitives using branded opaque types
Implements strict TypeScript practices: eliminates 'any' types with unknown guards, Zod validation, Result types, discriminated unions, branded types, and TS 5.5+ features.
Provides TypeScript functional patterns for ADTs, discriminated unions, Result/Option types, branded types. Use for state machines, type-safe domain models, and error handling.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Prevent mixing semantically distinct primitives using branded opaque types
declare const brand: unique symbol;
type Brand<T, B extends string> = T & { readonly [brand]: B };
type UserId = Brand<string, 'UserId'>;
type PostId = Brand<string, 'PostId'>;
type Email = Brand<string, 'Email'>;
function UserId(id: string): UserId {
if (!id || id.length === 0) throw new Error('Invalid user ID');
return id as UserId;
}
function Email(value: string): Email {
if (!value.includes('@')) throw new Error('Invalid email');
return value as Email;
}
function getUser(id: UserId): Promise<User> {
/* ... */
}
function getPost(id: PostId): Promise<Post> {
/* ... */
}
const userId = UserId('usr_123');
const postId = PostId('post_456');
getUser(userId); // OK
getUser(postId); // Error: PostId not assignable to UserId
getUser('raw'); // Error: string not assignable to UserId
import { z } from 'zod';
const UserIdSchema = z
.string()
.min(1)
.transform((val) => val as UserId);
const EmailSchema = z
.string()
.email()
.transform((val) => val as Email);
const userId = UserIdSchema.parse(input); // Type: UserId
type Cents = Brand<number, 'Cents'>;
type Dollars = Brand<number, 'Dollars'>;
function toDollars(cents: Cents): Dollars {
return (cents / 100) as Dollars;
}
function charge(amount: Cents): void {
/* ... */
}
charge(500 as Cents); // OK — explicit branding
charge(5 as Dollars); // Error — cannot mix units
type Branded<T, B> = T & { __brand: B };
// Shorthand branded types
type Timestamp = Branded<number, 'Timestamp'>;
type Latitude = Branded<number, 'Latitude'>;
type Longitude = Branded<number, 'Longitude'>;
type ValidatedInput = Brand<string, 'ValidatedInput'>;
function validateInput(raw: string): ValidatedInput {
const sanitized = raw.trim().slice(0, 1000);
if (sanitized.length === 0) throw new Error('Empty input');
return sanitized as ValidatedInput;
}
function processInput(input: ValidatedInput): void {
// Caller guarantees input has been validated
}
TypeScript uses structural typing — two types with the same shape are interchangeable. Branded types add a phantom property (never accessed at runtime) that makes structurally identical types incompatible. This simulates nominal typing.
The brand is invisible at runtime. The & { readonly [brand]: B } intersection exists only at the type level. The runtime value is still a plain string or number. There is zero overhead.
Validation boundaries: Branded types are most valuable at system boundaries — API input validation, database ID parsing, configuration loading. Once data is branded, downstream functions can trust it without re-validating.
unique symbol vs string brands: Using unique symbol ensures the brand property cannot collide with real properties. String brands (like __brand: 'UserId') are simpler but theoretically could collide with objects that have a __brand property.
Trade-offs:
as BrandedType casts bypass validation — only brand values through validated constructor functionsWhen NOT to use branded types:
https://typescriptlang.org/docs/handbook/2/types-from-types.html