Skip to content

feat(server,contract): narrow TMeta through .meta() chains#1550

Open
elv1n wants to merge 1 commit into
middleapi:mainfrom
elv1n:feat/meta-type-narrowing
Open

feat(server,contract): narrow TMeta through .meta() chains#1550
elv1n wants to merge 1 commit into
middleapi:mainfrom
elv1n:feat/meta-type-narrowing

Conversation

@elv1n
Copy link
Copy Markdown

@elv1n elv1n commented May 11, 2026

Motivation

~orpc.meta was typed as the wide declared schema rather than the values actually set, so there was no way to read the concrete meta a procedure carries.

Summary

.meta() now narrows TMeta to Omit<TMeta, keyof U> & U on each call. The declared schema lives on a new TMetaDef generic that .meta() constrains against via Partial<TMetaDef>, so unknown keys and wrong types are still rejected.

TMetaDef defaults to TMeta, so positional generics and downstream consumers (Procedure, Middleware, Router, plugins) are unchanged. mergeMeta returns MergedMeta<T, U>.

Summary by CodeRabbit

  • Type Improvements
    • Enhanced metadata typing with improved support for partial updates across builder chains.
    • Refined metadata merging behavior for more accurate type inference when composing metadata through fluent API calls.
    • Strengthened type-level validation for metadata schema adherence during chained operations, with better IDE autocomplete and error messaging.

Review Change Stack

.meta() now returns a builder whose TMeta reflects the values actually
passed in: Omit<TMeta, keyof U> & U.

.meta() takes Partial<TMetaDef> and constraint is in a new TMetaDef generic - only changed fields must be passed.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request javascript Pull requests that update javascript code labels May 11, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ccf0c25-ca60-49be-a66d-3d1de28e143c

📥 Commits

Reviewing files that changed from the base of the PR and between 0010bec and b1839c6.

📒 Files selected for processing (11)
  • packages/contract/src/builder-variants.test-d.ts
  • packages/contract/src/builder-variants.ts
  • packages/contract/src/builder.test-d.ts
  • packages/contract/src/builder.ts
  • packages/contract/src/meta.ts
  • packages/server/src/builder-variants.test-d.ts
  • packages/server/src/builder-variants.ts
  • packages/server/src/builder.test-d.ts
  • packages/server/src/builder.ts
  • packages/server/src/procedure-decorated.test-d.ts
  • packages/server/src/procedure-decorated.ts

📝 Walkthrough

Walkthrough

The PR introduces a metadata definition type parameter TMetaDef separated from the merged metadata type TMeta across all builder APIs in both contract and server packages. The .meta() method changes from accepting a full type to accepting a partial definition, which is merged into the result using the new MergedMeta<T, U> type alias. This pattern is propagated through every builder variant and fluent method.

Changes

Metadata Definition Type Propagation Across Builders

Layer / File(s) Summary
Meta Type Foundation
packages/contract/src/meta.ts
Introduces MergedMeta<T, U> = Omit<T, keyof U> & U type alias and updates mergeMeta function signature to accept two potentially different meta types and return MergedMeta<T, U> instead of a single T.
Contract Builder Core
packages/contract/src/builder.ts
ContractBuilder adds TMetaDef extends Meta = TMeta generic parameter; .meta() changes from meta(meta: TMeta) to meta<const U extends Partial<TMetaDef>>(meta: U) returning MergedMeta<TMeta, U>; all fluent methods now return builders with TMetaDef preserved.
Contract Procedure Variants
packages/contract/src/builder-variants.ts
All four procedure builder variants add TMetaDef generic, update .meta() to accept Partial<TMetaDef> with MergedMeta result, and thread TMetaDef through stage transitions and error handling.
Server Builder Core
packages/server/src/builder.ts
Builder adds TMetaDef extends Meta = TMeta generic; .meta() now accepts Partial<TMetaDef> and returns MergedMeta<TMeta, U> merged result; all methods propagate TMetaDef in return types.
Server Builder Variants
packages/server/src/builder-variants.ts
All builder and router variants add TMetaDef extends Meta = TMeta generic and thread it through method return types for meta handling, error chaining, middlewares, and routing options.
Decorated Procedure
packages/server/src/procedure-decorated.ts
DecoratedProcedure adds TMetaDef extends Meta = TMeta generic; .meta() signature changes to accept Partial<TMetaDef> and return MergedMeta<TMeta, U>; fluent methods propagate TMetaDef.
Type Tests
packages/contract/src/builder.test-d.ts, packages/contract/src/builder-variants.test-d.ts, packages/server/src/builder.test-d.ts, packages/server/src/builder-variants.test-d.ts, packages/server/src/procedure-decorated.test-d.ts
All test files updated to expect .meta() calls to return MergedMeta<BaseMeta, {...}> instead of plain BaseMeta; server builder tests add coverage for chaining accumulation, override semantics, schema enforcement, and variable-propagating helper composition.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • middleapi/orpc#223: Modifies the same DecoratedProcedure APIs (callable/actionable return types and related type-tests).
  • middleapi/orpc#244: Modifies builder/meta type signatures in packages/server/src/builder.ts (.meta / $meta APIs and related builder interfaces).
  • middleapi/orpc#247: Modifies ContractBuilder.$meta type signatures addressing meta-type shaping and return-type typing.

Suggested labels

size:XL

Poem

🐰 A builder chain, so flexible and grand,
Now metadata definitions hold their stand—
Partial schemas merge with grace,
Threading types through every space,
Spread semantics bloom and expand! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing metadata narrowing through .meta() chains by adding TMetaDef generic parameter.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a TMetaDef generic parameter across the contract and server builders to support metadata narrowing while preserving the original metadata definition, alongside a new MergedMeta utility type. Feedback focuses on ensuring that this generic is explicitly propagated through class constructors in methods like .meta(), $route(), and errors() to prevent incorrect type inference and maintain consistent type information across the builder chain.

Comment thread packages/contract/src/builder.ts
Comment thread packages/server/src/builder.ts
Comment thread packages/contract/src/builder.ts
Comment thread packages/contract/src/builder.ts
Comment thread packages/contract/src/builder.ts
Comment thread packages/server/src/procedure-decorated.ts
@elv1n
Copy link
Copy Markdown
Author

elv1n commented May 12, 2026

Gemini is wrong but if helpful I can add a test like

  it('.meta declared schema survives across .use and .errors transitions', () => {
    type TabConfig = { id: string }
    type Decl = { mode?: 'dev' | 'prod', tab?: TabConfig, retries?: number }
    const cfgA: TabConfig = { id: 'a' }

    const localBase = builder
      .$meta<Decl>({})
      .meta({ tab: cfgA });

    const result = localBase
      .use(({ next }) => next({}))
      .errors({ EXTRA: { message: 'EXTRA' } })
      .meta({ mode: 'dev' })
      .meta({ retries: 3 })

    expectTypeOf(result['~orpc'].meta.tab).toEqualTypeOf<TabConfig>()
    expectTypeOf(result['~orpc'].meta.mode).toEqualTypeOf<'dev'>()
    expectTypeOf(result['~orpc'].meta.retries).toEqualTypeOf<3>()

    builder
      .$meta<Decl>({})
      .meta({ tab: cfgA })
      .use(({ next }) => next({}))
      // @ts-expect-error - declared schema still enforced after .use
      .meta({ unknown: 1 })
  })

@dinwwwh
Copy link
Copy Markdown
Member

dinwwwh commented May 12, 2026

Thanks for the contribution!

A few concerns:

  1. Real-world use case - Could you share a concrete example of where this is needed?

  2. Required meta fields - This makes all meta fields partial, which breaks existing required field contracts.

  3. Type system complexity - The builder/procedure generics are already complex enough to cause TS to silently fall back to any in large projects. Generic meta types are being removed in v2 for this reason, so I don't think this PR fits the roadmap.

Let me know your thoughts!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request javascript Pull requests that update javascript code size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants