Skip to content

Improve internal type safety with nominal types #581

Description

@colincasey

Overview

Introduce nominal (opaque) types for domain-specific values used internally throughout tough-cookie. These types enforce that values pass through validation/construction before use, preventing bugs like passing raw domains where canonicalized ones are expected, or mixing up seconds and milliseconds.

Motivation

Many internal functions accept plain string, number, or boolean for values that have specific semantic constraints defined by RFC 6265 and RFC 6265bis. For example:

  • domainMatch() expects canonicalized domains but accepts any string
  • pathMatch() expects paths starting with / but accepts any string
  • Cookie.sameSite accepts any string but only 'strict' | 'lax' | 'none' are valid
  • Cookie.maxAge is in seconds but nothing in the type system distinguishes it from milliseconds

Nominal type pattern

Each nominal type uses a unique symbol brand + namespace pattern:

// The unique symbol is NOT exported — external code can't reference it
declare const tag: unique symbol;

export type CanonicalDomain = string & { readonly [tag]: true };

export namespace CanonicalDomain {
  export function parse(input: string): CanonicalDomain | undefined {
    // validation + canonicalization logic
    return result as CanonicalDomain;
  }
}

This gives us:

  • Zero runtime cost — values remain plain strings/numbers at runtime
  • Type.parse() API — the only way to construct a nominal value from external modules
  • Compile-time safety — prevents accidental misuse without as any as Type double-assertion
  • Declaration merging — the type and namespace share the same name for a clean API

Public API compatibility

All changes target internal code. The strategy is:

  1. Exported functions keep their current string-based signatures as thin wrappers
  2. Internal functions use nominal types (the "pure algorithm" layer)
  3. Cookie/Store class properties remain plain string | null publicly
  4. String literal unions (SameSiteLevel, PrefixSecurityMode) may narrow public types where the current string type is demonstrably incorrect

Sub-issues

High priority:

Medium priority:

Lower priority:

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    6265bisOfficially proposed changes to RFC 6265

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions