/skills/fuz-stack/references/type-utilities
  • docs
  • skills
  • fuz-stack
    • Async Patterns
    • Code Generation
    • Common Utilities
    • CSS Patterns
    • Dependency Injection
    • Documentation System
    • File Organization
    • mdz — Strict Markdown Dialect
    • Approved npm Dependencies
    • Path References in Documentation
    • Approved Rust Dependencies
    • Rust Patterns for the Fuz Ecosystem
    • Rust Performance Patterns
    • Rust Spine & Consumer Servers
    • Svelte 5 Patterns
    • Task Patterns
    • Testing Patterns
    • TSDoc Comment Style Guide
    • Twin Implementations (TS ↔ Rust)
    • Type Utilities
    • WASM Patterns for the Fuz Ecosystem
    • Zod Schemas
  • grimoire
  • tools
  • hash

Type Utilities

TypeScript type helpers in @fuzdev/fuz_util/types.ts — nominal typing, stricter standard utilities, and selective partial types.

Nominal Typing

TypeScript uses structural typing — two types with the same shape are interchangeable. Nominal typing adds invisible brands to distinguish them.

Flavored (loose)

Flavored<TValue, TName> adds an optional invisible brand. Unflavored base types are assignable without casting, but different flavors are incompatible. Primary nominal typing approach:

// Implementation: declare const FlavoredSymbol: unique symbol; interface Flavor<T> { readonly [FlavoredSymbol]?: T; // optional — base types still assignable } type Flavored<TValue, TName> = TValue & Flavor<TName>;type Email = Flavored<string, 'Email'>; type Address = Flavored<string, 'Address'>; const email1: Email = 'foo@bar.com'; // ok — plain string is fine const email2: Email = 'foo' as Address; // error — Address !== Email

Real uses in fuz_util:

// fuz_util/path.ts export type PathId = Flavored<string, 'PathId'>; // fuz_util/git.ts export type GitOrigin = Flavored<string, 'GitOrigin'>; export type GitBranch = Flavored<string, 'GitBranch'>; // fuz_util/colors.ts export type Hue = Flavored<number, 'Hue'>; // [0, 1] export type Saturation = Flavored<number, 'Saturation'>; // [0, 1] export type Lightness = Flavored<number, 'Lightness'>; // [0, 1] export type Red = Flavored<number, 'Red'>; // [0, 255] export type Green = Flavored<number, 'Green'>; // [0, 255] export type Blue = Flavored<number, 'Blue'>; // [0, 255] // fuz_util/url.ts export type Url = Flavored<string, 'Url'>;

Also: BlogPostId (fuz_blog), InputPath (gro), VocabName/ReorderableId (zzz).

Branded (strict)

Branded<TValue, TName> adds a required brand. Plain base types NOT assignable — must cast:

// Implementation: declare const BrandedSymbol: unique symbol; interface Brand<T> { readonly [BrandedSymbol]: T; // required — base types NOT assignable } type Branded<TValue, TName> = TValue & Brand<TName>;type PhoneNumber = Branded<string, 'PhoneNumber'>; const phone1: PhoneNumber = '555-1234'; // error — must cast const phone2: PhoneNumber = '555-1234' as PhoneNumber; // ok

Exported but unused in the ecosystem: in practice, use Flavored for compile-time nominal typing and Zod .brand() for runtime-validated types.

Choosing between them

TypeBase assignable?SafetyUse when
FlavoredYes (no cast)LooseIDs, paths, ergonomic APIs
BrandedNo (cast needed)StrictValidated data, security-sensitive

Zod .brand() — runtime-validated nominal types

For types needing runtime validation, use Zod .brand() (distinct from fuz_util's Branded):

// fuz_util/id.ts export const Uuid = z.uuid().brand('Uuid'); export type Uuid = z.infer<typeof Uuid>; // fuz_util/datetime.ts export const Datetime = z.iso.datetime().brand('Datetime'); export type Datetime = z.infer<typeof Datetime>; // zzz/diskfile_types.ts export const DiskfilePath = z .string() .refine((p) => is_path_absolute(p), {message: 'path must be absolute'}) .brand('DiskfilePath'); export type DiskfilePath = z.infer<typeof DiskfilePath>;

Flavored/Branded are compile-time only (no runtime check); Zod .brand() brands a schema that also validates — Uuid rejects non-UUID strings at parse time — so reach for .brand() when the value crosses a runtime boundary. (zzz re-imports fuz_util's Uuid rather than defining its own.)

See ./zod-schemas for full Zod schema conventions including branded types.

Strict Utility Types

OmitStrict

Stricter Omit — K must be an actual key of T:

type OmitStrict<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

Standard Omit accepts any string for K (typos compile silently); OmitStrict catches them. Widely used in fuz_ui, fuz_app, zzz.

PickUnion and KeyofUnion

Standard Pick and keyof don't distribute over unions. These do:

type KeyofUnion<T> = T extends unknown ? keyof T : never; type PickUnion<T, K extends KeyofUnion<T>> = T extends unknown ? K & keyof T extends never ? never : Pick<T, K & keyof T> : never;type A = {x: number; y: string}; type B = {x: number; z: boolean}; type Keys = KeyofUnion<A | B>; // 'x' | 'y' | 'z' type Picked = PickUnion<A | B, 'x'>; // {x: number} | {x: number}

Partial Variants

PartialExcept

Everything optional EXCEPT specified keys:

type PartialExcept<T, K extends keyof T> = {[P in K]: T[P]} & { [P in Exclude<keyof T, K>]?: T[P]; };interface User { id: string; name: string; email: string; } type UserUpdate = PartialExcept<User, 'id'>; // { id: string; name?: string; email?: string; }

PartialOnly

Only specified keys optional:

type PartialOnly<T, K extends keyof T> = {[P in K]?: T[P]} & { [P in Exclude<keyof T, K>]: T[P]; };

PartialValues

Values of T become partial (not the keys):

type PartialValues<T> = { [P in keyof T]: Partial<T[P]> };

Modifier Types

Assignable

Removes readonly:

type Assignable<T, K extends keyof T = keyof T> = { -readonly [P in K]: T[P] };

Used in zzz for self-referential initialization:

// zzz/frontend.svelte.ts (this as Assignable<typeof this, 'app'>).app = this;

Extraction Types

ClassConstructor

type ClassConstructor<TInstance, TArgs extends Array<any> = Array<any>> = new (...args: TArgs) => TInstance;

Used in zzz Cell registry:

// zzz/cell_registry.svelte.ts readonly #constructors: Map<string, ClassConstructor<Cell>> = new Map();

ArrayElement

type ArrayElement<T> = T extends ReadonlyArray<infer U> ? U : never;type Item = ArrayElement<Array<{id: string}>>; // {id: string}

Defined and NotNull

type Defined<T> = T extends undefined ? never : T; type NotNull<T> = T extends null ? never : T;

Quick Reference

TypePurpose
Flavored<TValue, TName>Loose nominal typing (no cast from base)
Branded<TValue, TName>Strict nominal typing (cast required, ecosystem uses Zod .brand() instead)
OmitStrict<T, K>Omit with key validation
PickUnion<T, K>Pick that distributes over unions
KeyofUnion<T>keyof that distributes over unions
PartialExceptAll optional except specified keys
PartialOnlyOnly specified keys optional
PartialValuesValues of T become partial
AssignableRemove readonly
ClassConstructorMatch constructor functions
ArrayElementExtract element type from array
DefinedExclude undefined
NotNullExclude null