Samuel Goldenbaum
Samuel Goldenbaum

Reputation: 18939

net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 with tRPC subscriptions in Hono/Bun

When using the Hono (Bun) tRPC middleware as per the instructions and setting up a tRPC subscription, the following error is thrown in the console, and a loop is created that eventually leads to a memory leak at the server:

net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)

enter image description here

I don't see this with the equivalent Node HTTP server. Is anyone else using tRPC subscriptions successfully with Hono?

Repo here to reproduce

server.ts

import { initTRPC } from '@trpc/server'
import { cors } from 'hono/cors'
import { z } from 'zod'
import { EventEmitter, on } from 'events'
import { randomUUID } from 'crypto'
import superjson from 'superjson'
import { Hono } from 'hono'
import { EVENT, Widget } from '../common'
import { trpcServer } from '@hono/trpc-server'

const t = initTRPC.create({
  transformer: superjson
})
const eventEmitter = new EventEmitter()

const publicProcedure = t.procedure
const router = t.router

const appRouter = router({
  create: publicProcedure
    .input(
      z.object({
        name: z.string()
      })
    )
    .mutation(({ input }) => {
      const widget: Widget = {
        ...input,
        id: randomUUID(),
        createdAt: new Date().toDateString()
      } satisfies Widget

      eventEmitter.emit(EVENT.CREATE, widget)
    }),
  onCreate: publicProcedure.subscription(async function* (opts) {
    for await (const [data] of on(eventEmitter, EVENT.CREATE)) {
      const widget = data as Widget
      yield widget
    }
  })
})

export type AppRouter = typeof appRouter

const app = new Hono().use(cors()).use(
  '/trpc/*',
  trpcServer({
    router: appRouter
  })
)

export default {
  port: 3001,
  fetch: app.fetch
}

client

import React, { useEffect, useState } from 'react'
import './App.css'
import { faker } from '@faker-js/faker'
import { Widget } from '../../common'
import {
  createTRPCClient,
  httpBatchLink,
  loggerLink,
  splitLink,
  unstable_httpSubscriptionLink
} from '@trpc/client'
import { AppRouter } from 'trpc-subscription-demo-hono-server'
import superjson from 'superjson'

const url = 'http://localhost:3001/trpc'

const trpc = createTRPCClient<AppRouter>({
  links: [
    loggerLink(),
    splitLink({
      condition: (op) => op.type === 'subscription',
      true: unstable_httpSubscriptionLink({
        url,
        transformer: superjson
      }),
      false: httpBatchLink({
        url,
        transformer: superjson
      })
    })
  ]
})

function App() {
  const [widgets, setWidgets] = useState<Widget[]>([])

  useEffect(() => {
    trpc.onCreate.subscribe(undefined, {
      onData: (data) => {
        setWidgets((widgets) => [...widgets, data])
      },
      onError: (err) => {
        console.error('subscribe error', err)
      }
    })
  }, [])

  return (
    <div className="App">
      <header className="App-header">Widgets</header>

      <button
        onClick={() => {
          trpc.create.mutate({ name: faker.commerce.productName() })
        }}
      >
        Create Widget
      </button>

      <hr />

      <ul>
        {widgets.map((widget) => (
          <li key={widget.id}>{widget.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default App

Upvotes: 0

Views: 60

Answers (0)

Related Questions