Skip to content

fix(types): preserve define helper properties - #855

Merged
harlan-zw merged 2 commits into
mainfrom
fix/define-helper-inference
Jul 19, 2026
Merged

fix(types): preserve define helper properties#855
harlan-zw merged 2 commits into
mainfrom
fix/define-helper-inference

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

Related to #822

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

defineLink() and defineScript() validate precise inputs but currently return the broad Link and Script unions, erasing literal and custom properties. This preserves validated properties for single known variants and custom inputs while keeping union discriminants and existing Link/Script assignability unchanged.

Summary by CodeRabbit

  • Bug Fixes
    • Improved type validation for link and script helpers.
    • Preserved stricter, context-specific property requirements while supporting custom values.
    • Enhanced type inference so returned configurations retain validated literal types.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@harlan-zw, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 42baaa5d-5f1c-48e3-850b-71d2383d9f09

📥 Commits

Reviewing files that changed from the base of the PR and between b042a27 and a286e64.

📒 Files selected for processing (2)
  • packages/unhead/src/define.ts
  • packages/unhead/test/unit/types.test.ts
📝 Walkthrough

Walkthrough

defineLink and defineScript now return conditional types that preserve strict rel and type inference. Type tests verify literal property preservation and assignability to the corresponding public types.

Changes

Typed helper contracts

Layer / File(s) Summary
Derived helper return types
packages/unhead/src/define.ts
Adds conditional DefinedLink and DefinedScript types and updates both helper return signatures and casts.
Type contract validation
packages/unhead/test/unit/types.test.ts
Adds type assertions covering custom link relations, script shapes, literal properties, and Link/Script assignability.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • unjs/unhead#729: Tightens the underlying Link and Script unions used by these helper return types.
  • unjs/unhead#735: Updates the same typed helpers and their InferLink/InferScript inference.
  • unjs/unhead#765: Addresses union-based rel and type inference for these helpers.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main type-preservation change.
Description check ✅ Passed The description matches the template with linked issue, change type, and a clear summary of the type-inference fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/define-helper-inference

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size

No notable changes

All bundles (14)
Bundle Gzipped Brotli Raw
Core
Client (Minimal) 5.3 kB 4.8 kB 13.3 kB
Client (Full) 8.6 kB 7.9 kB 22.5 kB
Client (Self-Contained) 5.6 kB 5.1 kB 14.1 kB
Server (Minimal) 4.8 kB 4.4 kB 12.1 kB
Server (Self-Contained) 5.2 kB 4.6 kB 12.8 kB
Vue
Vue Client (Minimal) 5.8 kB 5.3 kB 14.4 kB
Vue Client (Full) 9.4 kB 8.5 kB 24.5 kB
Vue Server (Minimal) 5.3 kB 4.8 kB 13.1 kB
React
React Client (Minimal) 5.7 kB 5.2 kB 14.3 kB
React Client (Full) 9.5 kB 8.6 kB 24.9 kB
React Server (Minimal) 5.1 kB 4.6 kB 12.8 kB
Schema.org
Schema.org (Minimal) 9.9 kB 9 kB 27.1 kB
Schema.org Imports 0.1 kB 0.1 kB 0.1 kB
Schema.org Vue Meta 0.4 kB 0.3 kB 0.8 kB

⚡ Performance (directional)

No significant change (within CI noise)

All benchmarks (14)
Benchmark PR Δ RME
SSR render (CPU) 0.354 ms ~ noise ±9.6%
SSR render (wall) 0.261 ms ~ noise ±4.9%
SSR allocated / render 252.4 KiB ~ noise
Schema.org cached render (CPU) 0.336 ms ~ noise ±6.7%
Schema.org cached render (wall) 0.247 ms ~ noise ±2.6%
Schema.org cached allocated / render 148 KiB ~ noise
Streaming wrapStream drain (CPU) 0.232 ms ~ noise ±5.3%
Streaming wrapStream drain (wall) 0.147 ms ~ noise ±5.6%
Streaming allocated / drain 160.7 KiB ~ noise
Streaming suspense chunk (CPU) 0.011 ms ~ noise ±3.7%
Streaming allocated / suspense chunk 4.6 KiB ~ noise
CSR DOM mutations / nav 38 ~ noise
CSR re-render (CPU) 0.777 ms ~ noise ±5.3%
CSR re-render (wall) 0.448 ms ~ noise ±1.9%

Baseline: main @ 25f09ee · 2026-07-19 · gzipped is the headline size metric · perf is directional (shared-runner, gated)

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
packages/unhead/src/define.ts (1)

4-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align DefinedLink union distribution with DefinedScript.

Currently, DefinedLink behaves differently from DefinedScript when provided a discriminated union of objects (e.g., cond ? { rel: 'preload' } : { rel: 'prefetch' }).

Because IsUnion<T['rel']> does not evaluate on a naked type parameter T, it prevents TypeScript from distributing the conditional type over the union. As a result, unions of objects collapse into the broad Link type, whereas DefinedScript correctly preserves the exact shapes of its constituent objects.

To ensure consistent distribution behavior and properly preserve specific literal properties for object unions, consider structuring DefinedLink with T extends { rel: infer U } to trigger conditional distribution:

💡 Proposed refactor to align distribution behavior
-type DefinedLink<T extends { rel: string }> = IsUnion<T['rel']> extends false
-  ? T extends Link ? T : Link & Omit<T, 'rel'>
-  : Link & Omit<T, 'rel'>
+type DefinedLink<T extends { rel: string }> = T extends { rel: infer U }
+  ? IsUnion<U> extends false
+    ? T extends Link ? T : Link & Omit<T, 'rel'>
+    : Link & Omit<T, 'rel'>
+  : never
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/unhead/src/define.ts` around lines 4 - 6, Update DefinedLink to use
a distributive conditional based on T extending { rel: infer U }, matching the
distribution pattern used by DefinedScript. Preserve the existing Link and
Omit<T, 'rel'> behavior for each constituent while ensuring discriminated unions
retain their specific object shapes and literal properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/unhead/src/define.ts`:
- Around line 4-6: Update DefinedLink to use a distributive conditional based on
T extending { rel: infer U }, matching the distribution pattern used by
DefinedScript. Preserve the existing Link and Omit<T, 'rel'> behavior for each
constituent while ensuring discriminated unions retain their specific object
shapes and literal properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69174678-ed30-4c7e-9f49-0268dbd66de2

📥 Commits

Reviewing files that changed from the base of the PR and between 25f09ee and b042a27.

📒 Files selected for processing (2)
  • packages/unhead/src/define.ts
  • packages/unhead/test/unit/types.test.ts

@harlan-zw
harlan-zw merged commit 216a017 into main Jul 19, 2026
9 checks passed
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.

1 participant