Skip to content

Conversation

@unnoq
Copy link
Member

@unnoq unnoq commented Jul 2, 2025

Summary by CodeRabbit

  • New Features

    • Added support for async iterable outputs in procedures, enabling streaming events with event metadata.
    • Subscription procedures can now accept and utilize a lastEventId parameter for event streaming.
  • Tests

    • Introduced new tests to verify event iterator behavior, including event metadata and cleanup logic in subscriptions.
    • Enhanced test data to yield tracked events with identifiers and metadata.
  • Chores

    • Updated dependencies and TypeScript project references to include the client package.

@vercel
Copy link

vercel bot commented Jul 2, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
orpc ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 2, 2025 9:58am

@coderabbitai
Copy link

coderabbitai bot commented Jul 2, 2025

Caution

Review failed

The pull request is closed.

"""

Walkthrough

This update enhances the @orpc/trpc package by adding support for async iterable outputs and event metadata in the tRPC-to-oRPC conversion logic. It introduces new type transformations, updates procedure handling to merge lastEventId into inputs, and expands tests to cover event iterator scenarios. Project dependencies and references were also updated.

Changes

File(s) Change Summary
packages/trpc/package.json, packages/trpc/tsconfig.json Added @orpc/client as a dependency and project reference.
packages/trpc/src/to-orpc-router.ts Enhanced to support async iterable outputs, event metadata, and merged lastEventId in procedure inputs.
packages/trpc/src/to-orpc-router.test.ts Added tests for event iterator subscriptions, event metadata, and cleanup logic.
packages/trpc/src/to-orpc-router.test-d.ts Updated type assertions for subscription procedures to expect more precise async iterator output types.
packages/trpc/tests/shared.ts Added tracked event yields in the subscribe procedure for more comprehensive event streaming tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant oRPC Procedure
    participant tRPC Procedure

    Client->>oRPC Procedure: Call subscription with lastEventId
    oRPC Procedure->>oRPC Procedure: Merge lastEventId into input (if applicable)
    oRPC Procedure->>tRPC Procedure: Invoke with merged input
    tRPC Procedure-->>oRPC Procedure: Return async iterable
    loop For each event in iterable
        oRPC Procedure->>oRPC Procedure: Wrap event with event metadata (if tracked)
        oRPC Procedure-->>Client: Yield event with metadata
    end
    oRPC Procedure-->>Client: Signal completion
Loading

Possibly related PRs

  • unnoq/orpc#699: Introduced initial tRPC-to-ORPC router conversion logic, which is extended here to handle async iterables and event metadata.

Suggested labels

size:XL

Poem

A rabbit hopped through streams of code,
Where events and data now freely flowed.
With tracked and pong, and metadata bright,
Async iterators stream into the night.
Dependencies linked, tests now enhanced—
This bunny’s code, it truly danced!
🐇✨
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07fab90 and 93521fa.

📒 Files selected for processing (1)
  • packages/trpc/src/to-orpc-router.ts (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 2, 2025
Copy link
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.

Summary of Changes

Hello @unnoq, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the toORPCRouter utility by adding comprehensive support for tRPC subscriptions, allowing them to function seamlessly as event iterators within the ORPC ecosystem. The changes ensure proper handling of streamed data, including tRPC's tracked events and the propagation of lastEventId for stream resumption, thereby bridging the gap between tRPC's subscription model and ORPC's event-driven capabilities.

Highlights

  • Event Iterator Support: The toORPCRouter utility now fully supports tRPC subscriptions that return AsyncIterable objects, allowing them to be consumed as event iterators within the ORPC framework.
  • Tracked Data Transformation: tRPC's tracked data (used for associating event IDs with streamed data) is now correctly transformed into ORPC's event metadata format, ensuring that event IDs are preserved and accessible on the ORPC client side.
  • Last Event ID Propagation: The lastEventId from the ORPC context is now properly passed into the tRPC procedure's input, enabling tRPC subscriptions to resume from a specific event ID when converted to ORPC procedures.
  • Type System Enhancements: Updated type definitions, including the introduction of experimental_ToORPCOutput and the use of AsyncIteratorObject, provide more accurate type inference for event iterator outputs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Jul 2, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jul 2, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@716

@orpc/client

npm i https://pkg.pr.new/@orpc/client@716

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@716

@orpc/experimental-durable-event-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-event-iterator@716

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@716

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@716

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@716

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@716

@orpc/react

npm i https://pkg.pr.new/@orpc/react@716

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@716

@orpc/server

npm i https://pkg.pr.new/@orpc/server@716

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@716

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@716

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@716

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@716

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@716

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@716

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@716

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@716

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@716

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@716

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@716

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@716

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@716

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@716

commit: 93521fa

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/trpc/src/to-orpc-router.ts (1)

99-101: Enhance input validation for lastEventId merging.

The current logic merges lastEventId when input is undefined or a plain object. Consider adding validation to ensure lastEventId doesn't conflict with existing properties.

        const trpcInput = lastEventId !== undefined && (input === undefined || isObject(input))
-         ? { ...input, lastEventId }
+         ? { ...input, lastEventId: input?.lastEventId ?? lastEventId }
          : input

This prevents accidentally overwriting an existing lastEventId property in the input.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 083a799 and fa61e9c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • packages/trpc/package.json (1 hunks)
  • packages/trpc/src/to-orpc-router.test-d.ts (3 hunks)
  • packages/trpc/src/to-orpc-router.test.ts (2 hunks)
  • packages/trpc/src/to-orpc-router.ts (3 hunks)
  • packages/trpc/tests/shared.ts (2 hunks)
  • packages/trpc/tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/trpc/src/to-orpc-router.test-d.ts (2)
packages/server/src/procedure.ts (1)
  • Procedure (55-71)
packages/trpc/tests/shared.ts (2)
  • TRPCContext (6-6)
  • TRPCMeta (7-10)
packages/trpc/src/to-orpc-router.test.ts (2)
packages/shared/src/iterator.ts (1)
  • isAsyncIteratorObject (4-10)
packages/trpc/tests/shared.ts (2)
  • trpcRouter (14-67)
  • t (12-12)
packages/trpc/src/to-orpc-router.ts (2)
packages/shared/src/object.ts (1)
  • isObject (31-39)
packages/client/src/event-iterator.ts (1)
  • mapEventIterator (4-43)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: lint
  • GitHub Check: publish-commit
🔇 Additional comments (15)
packages/trpc/tsconfig.json (1)

5-5: LGTM! Configuration change aligns with new dependency.

The addition of the client project reference is consistent with the new @orpc/client dependency added in the package.json.

packages/trpc/tests/shared.ts (2)

2-2: LGTM! Import addition supports event tracking functionality.

The tracked import is correctly added to support the new event iterator test scenarios.


37-38: LGTM! Test data for event iterators is well-structured.

The tracked events with sequential IDs and order metadata provide good test coverage for the new event iterator functionality.

packages/trpc/package.json (1)

41-41: LGTM! Dependency addition supports new client functionality.

The @orpc/client dependency is correctly added with the workspace pattern consistent with other @orpc packages.

packages/trpc/src/to-orpc-router.test-d.ts (3)

3-3: LGTM! Import addition supports tracked data types.

The TrackedData import is correctly added to support the refined type assertions for event iterators.


27-27: LGTM! Type refinement improves precision for async iterators.

The type change from AsyncIterable<string> to AsyncIteratorObject<'pong' | TrackedData<{ order: number }>, void, any> correctly reflects the specific types yielded by the subscription procedure.


38-38: LGTM! Consistent type refinement for lazy subscription.

The type change to AsyncIteratorObject<string> is consistent with the other subscription procedure type refinements.

packages/trpc/src/to-orpc-router.test.ts (5)

1-1: LGTM! Import additions support comprehensive event iterator testing.

The new imports (getEventMeta, tracked, z) are correctly added to support the event iterator test functionality.


9-11: LGTM! Proper test setup with mock cleanup.

The beforeEach hook ensures clean test state by clearing mocks, which is a good testing practice.


83-103: LGTM! Comprehensive test for subscription with tracked events.

The test thoroughly validates:

  • Async iterator functionality
  • Event sequence and completion
  • Tracked event structure with IDs and metadata
  • Event metadata extraction

The assertions are well-structured and cover the expected behavior.


105-133: LGTM! Thorough test coverage for lastEventId parameter handling.

The test effectively validates:

  • lastEventId merging with object inputs
  • lastEventId handling with undefined inputs
  • lastEventId behavior with primitive inputs

The mock usage and assertions are appropriate for verifying the parameter passing logic.


135-166: LGTM! Excellent test for AsyncIterable support with cleanup.

The test demonstrates proper handling of:

  • AsyncIterable return values (not just generators)
  • Cleanup logic execution via finally blocks
  • Iterator termination via return() method

The cleanup verification ensures proper resource management.

packages/trpc/src/to-orpc-router.ts (3)

2-7: LGTM: Import additions support new functionality.

The new imports are appropriate for the event iterator and metadata handling features being added.


13-16: LGTM: Type transformation correctly handles async iterables.

The conditional type properly transforms async iterable outputs to AsyncIteratorObject while preserving non-async types.


26-26: LGTM: Output schema type correctly applies the transformation.

The integration of experimental_ToORPCOutput in the procedure type definition is appropriate.

Comment on lines +97 to +102
handler: async ({ context, signal, path, input, lastEventId }) => {
try {
return await procedure({
const trpcInput = lastEventId !== undefined && (input === undefined || isObject(input))
? { ...input, lastEventId }
: input

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify input spreading behavior with complex objects.

The input merging logic looks correct, but spreading could have unexpected behavior with objects containing getters, symbols, or non-enumerable properties.


🏁 Script executed:

#!/bin/bash
# Description: Search for test cases that verify input merging behavior with complex objects
# Expected: Tests covering edge cases like objects with getters, symbols, or prototype pollution

rg -A 10 -B 5 "lastEventId.*input|input.*lastEventId" --type ts

Length of output: 17376


Add tests for input merging edge cases with complex objects

The current tests only cover primitive and plain‐object inputs. Since object spread only copies own, enumerable, string-keyed properties, please add unit tests in packages/trpc/src/to-orpc-router.test.ts to verify behavior when input contains:

  • Getter properties (e.g. Object.defineProperty(input, 'foo', { get: … }))
  • Symbol-keyed properties
  • Non-enumerable properties
  • Inherited (prototype) properties

This will ensure the merge { …input, lastEventId } behaves as expected in all edge cases.

🤖 Prompt for AI Agents
In packages/trpc/src/to-orpc-router.ts around lines 97 to 102, the code merges
input with lastEventId using object spread, which only copies own, enumerable,
string-keyed properties. To ensure correct behavior in edge cases, add unit
tests in packages/trpc/src/to-orpc-router.test.ts that cover inputs containing
getter properties, symbol-keyed properties, non-enumerable properties, and
inherited prototype properties. These tests should verify how the merge handles
these cases and confirm the output matches expected behavior.

Comment on lines 112 to 130
if (isAsyncIterable(output)) {
return mapEventIterator(output[Symbol.asyncIterator](), {
error: async error => error,
value: (value) => {
if (isTrackedEnvelope(value)) {
const [id, data] = value

return ORPC.withEventMeta({
id,
data,
} as TrackedData<unknown>, {
id,
})
}

return value
},
})
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Review the type cast safety and async iterator error handling.

The implementation handles async iterables correctly, but there are potential concerns:

  1. The type cast as TrackedData<unknown> assumes the tracked envelope structure is always valid
  2. The mapEventIterator error handling passes errors through unchanged - verify this aligns with expected error propagation

Consider adding runtime validation for the tracked envelope structure:

              if (isTrackedEnvelope(value)) {
                const [id, data] = value
+               
+               // Validate tracked envelope structure
+               if (typeof id !== 'string' && typeof id !== 'number') {
+                 throw new Error('Invalid tracked envelope: id must be string or number')
+               }

                return ORPC.withEventMeta({
                  id,
                  data,
-               } as TrackedData<unknown>, {
+               }, {
                  id,
                })
              }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isAsyncIterable(output)) {
return mapEventIterator(output[Symbol.asyncIterator](), {
error: async error => error,
value: (value) => {
if (isTrackedEnvelope(value)) {
const [id, data] = value
return ORPC.withEventMeta({
id,
data,
} as TrackedData<unknown>, {
id,
})
}
return value
},
})
}
if (isAsyncIterable(output)) {
return mapEventIterator(output[Symbol.asyncIterator](), {
error: async error => error,
value: (value) => {
if (isTrackedEnvelope(value)) {
const [id, data] = value
// Validate tracked envelope structure
if (typeof id !== 'string' && typeof id !== 'number') {
throw new Error('Invalid tracked envelope: id must be string or number')
}
return ORPC.withEventMeta({
id,
data,
}, {
id,
})
}
return value
},
})
}
🤖 Prompt for AI Agents
In packages/trpc/src/to-orpc-router.ts between lines 112 and 130, the code casts
the tracked envelope to TrackedData<unknown> without runtime validation, which
may cause unsafe assumptions. Add runtime checks to confirm the structure of the
tracked envelope before casting to ensure type safety. Also, review the error
handler in mapEventIterator to confirm that passing errors through unchanged
matches the expected error propagation behavior; adjust the error handling logic
if necessary to properly handle or transform errors.

Copy link
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 support for tRPC subscriptions using event iterators, including resumable subscriptions with tracked data. The changes handle passing lastEventId and transforming tRPC's TrackedData envelopes into oRPC events. The review identifies a type cast issue in to-orpc-router.ts and a potential type issue in the tests, which need to be addressed to ensure type safety and correctness.

Comment on lines +119 to +122

return ORPC.withEventMeta({
id,
data,
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The cast as TrackedData<unknown> is incorrect. The object { id, data } is not a tuple of type [string, unknown], which is what TrackedData<unknown> resolves to. Remove this cast to avoid potential type-related bugs.

Suggested change
return ORPC.withEventMeta({
id,
data,
return ORPC.withEventMeta({
id,
data,
}, {
id,
})

@unnoq unnoq merged commit dda04c5 into main Jul 2, 2025
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant