Skip to content

Proposal: NarrowBy<T, Filters extends { [K in Paths<T>]?: any }> #1397

@salisbury-espinosa

Description

@salisbury-espinosa

Type description + examples

1. The "Deep Nesting" Scenario

This is useful when you have deeply nested configuration objects or state trees where only one specific branch should be active.

type AppState = {
  user: {
    status: 'anonymous' | 'authenticated';
    data?: { name: string; email: string };
  };
  theme: {
    mode: 'light' | 'dark' | 'high-contrast';
    settings: {
      color: string;
      fontSize: number;
    };
  };
};

// Narrowing the user state to only 'authenticated'
type ActiveUserSession = NarrowBy<AppState, { 'user.status': 'authenticated' }>;

/* Result: user.status is strictly 'authenticated'. 
Note: theme remains a union of all modes because we didn't filter it.
*/

2. Narrowing by Non-Discriminator Fields

Because we are using Get<T, P>, you aren't limited to fields named type or kind. You can narrow by any value, including primitives.

type Shape = 
  | { kind: 'circle'; radius: number; color: 'red' | 'blue' }
  | { kind: 'square'; side: number; color: 'red' | 'green' };

// Get all red shapes regardless of 'kind'
type RedShapes = NarrowBy<Shape, { 'color': 'red' }>;

/* Result:
| { kind: 'circle'; radius: number; color: 'red' }
| { kind: 'square'; side: number; color: 'red' }
*/

3. The "API Response" Scenario

When working with polymorphic API responses (common in GraphQL or JSON:API), you can narrow down to the specific resource you are currently handling.

type SearchResult = {
  id: string;
  metadata: 
    | { __typename: 'User'; username: string; avatar: string }
    | { __typename: 'Post'; title: string; excerpt: string }
    | { __typename: 'Comment'; body: string; authorId: string };
  score: number;
};

// Extract only the "Post" results from a search
type PostResult = NarrowBy<SearchResult, { 'metadata.__typename': 'Post' }>;

/* Result: 
{ 
  id: string; 
  metadata: { __typename: 'Post'; title: string; excerpt: string }; 
  score: number; 
}
*/

Type source

No response

Search existing types and issues first

  • I tried my best to look for it

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions