Skip to content

Preserve discriminated unions when merging query relations - #156

Merged
KidkArolis merged 1 commit into
masterfrom
sgrowe/push-xtmssuvsuuxx
Oct 9, 2026
Merged

KidkArolis merged 1 commit into
masterfrom
sgrowe/push-xtmssuvsuuxx

Conversation

@sgrowe

@sgrowe sgrowe commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

How the merge works

For each possible item type, replace any fields supplied by the relations:

type MergeRelated<TItem, TRelated extends object> = TItem extends unknown
  ? Omit<TItem, keyof TRelated> & TRelated
  : never;
  • keyof TRelated gives the relation field names.
  • Omit<TItem, keyof TRelated> removes those fields from the original item.
  • & TRelated adds the relation fields with their new types.

The subtle part is TItem extends unknown. Every type matches unknown, but TypeScript has a special rule: this conditional processes each union member separately.

For example:

StringField | DateField | ReferenceField;

becomes effectively:

MergeStringField | MergeDateField | MergeReferenceField;

Each variant keeps its own properties and discriminator. Without the conditional, Omit processes the union as a whole and loses those relationships.

The never branch is the required "otherwise" part of the conditional; ordinary item types always take the first branch.

## How the merge works

For each possible item type, replace any fields supplied by the relations:

```ts
type MergeRelated<TItem, TRelated extends object> = TItem extends unknown
  ? Omit<TItem, keyof TRelated> & TRelated
  : never;
```

- `keyof TRelated` gives the relation field names.
- `Omit<TItem, keyof TRelated>` removes those fields from the original item.
- `& TRelated` adds the relation fields with their new types.

The subtle part is `TItem extends unknown`. Every type matches `unknown`,
but TypeScript has a special rule: this conditional processes **each union
member separately**.

For example:

```ts
StringField | DateField | ReferenceField;
```

becomes effectively:

```ts
MergeStringField | MergeDateField | MergeReferenceField;
```

Each variant keeps its own properties and discriminator. Without the
conditional, `Omit` processes the union as a whole and loses those
relationships.

The `never` branch is the required "otherwise" part of the conditional;
ordinary item types always take the first branch.
@sgrowe
sgrowe requested a review from KidkArolis October 8, 2026 14:33

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: low. Approved. Cursor Bugbot completed with no findings that need human review; this is a type-only union-distribution fix with compile-time coverage and no runtime behavior change.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver - Risk Based

@KidkArolis
KidkArolis merged commit e82b5d1 into master Oct 9, 2026
5 checks passed
@KidkArolis
KidkArolis deleted the sgrowe/push-xtmssuvsuuxx branch October 9, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants