\n
I don't see this with the equivalent Node HTTP server. Is anyone else using tRPC subscriptions successfully with Hono?
\nRepo here to reproduce
\nserver.ts
import { initTRPC } from '@trpc/server'\nimport { cors } from 'hono/cors'\nimport { z } from 'zod'\nimport { EventEmitter, on } from 'events'\nimport { randomUUID } from 'crypto'\nimport superjson from 'superjson'\nimport { Hono } from 'hono'\nimport { EVENT, Widget } from '../common'\nimport { trpcServer } from '@hono/trpc-server'\n\nconst t = initTRPC.create({\n transformer: superjson\n})\nconst eventEmitter = new EventEmitter()\n\nconst publicProcedure = t.procedure\nconst router = t.router\n\nconst appRouter = router({\n create: publicProcedure\n .input(\n z.object({\n name: z.string()\n })\n )\n .mutation(({ input }) => {\n const widget: Widget = {\n ...input,\n id: randomUUID(),\n createdAt: new Date().toDateString()\n } satisfies Widget\n\n eventEmitter.emit(EVENT.CREATE, widget)\n }),\n onCreate: publicProcedure.subscription(async function* (opts) {\n for await (const [data] of on(eventEmitter, EVENT.CREATE)) {\n const widget = data as Widget\n yield widget\n }\n })\n})\n\nexport type AppRouter = typeof appRouter\n\nconst app = new Hono().use(cors()).use(\n '/trpc/*',\n trpcServer({\n router: appRouter\n })\n)\n\nexport default {\n port: 3001,\n fetch: app.fetch\n}\n
\nclient
import React, { useEffect, useState } from 'react'\nimport './App.css'\nimport { faker } from '@faker-js/faker'\nimport { Widget } from '../../common'\nimport {\n createTRPCClient,\n httpBatchLink,\n loggerLink,\n splitLink,\n unstable_httpSubscriptionLink\n} from '@trpc/client'\nimport { AppRouter } from 'trpc-subscription-demo-hono-server'\nimport superjson from 'superjson'\n\nconst url = 'http://localhost:3001/trpc'\n\nconst trpc = createTRPCClient<AppRouter>({\n links: [\n loggerLink(),\n splitLink({\n condition: (op) => op.type === 'subscription',\n true: unstable_httpSubscriptionLink({\n url,\n transformer: superjson\n }),\n false: httpBatchLink({\n url,\n transformer: superjson\n })\n })\n ]\n})\n\nfunction App() {\n const [widgets, setWidgets] = useState<Widget[]>([])\n\n useEffect(() => {\n trpc.onCreate.subscribe(undefined, {\n onData: (data) => {\n setWidgets((widgets) => [...widgets, data])\n },\n onError: (err) => {\n console.error('subscribe error', err)\n }\n })\n }, [])\n\n return (\n <div className="App">\n <header className="App-header">Widgets</header>\n\n <button\n onClick={() => {\n trpc.create.mutate({ name: faker.commerce.productName() })\n }}\n >\n Create Widget\n </button>\n\n <hr />\n\n <ul>\n {widgets.map((widget) => (\n <li key={widget.id}>{widget.name}</li>\n ))}\n </ul>\n </div>\n )\n}\n\nexport default App\n\n
\n","author":{"@type":"Person","name":"Samuel Goldenbaum"},"upvoteCount":0,"answerCount":0,"acceptedAnswer":null}}Reputation: 18939
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)
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