H
Hono•2mo ago
Tony

RPC types not working well inside a monorepo

I have a monorepo setup with packages/api for my hono api and app/www for the client code. I have set up rpc well but i am getting Type instantiation is excessively deep and possibly infinite.ts. I have tried the following - I have set strict to true on both my api and client package - have set composite true in my api package and set path reference in my client package. The above two helped to infer the types in the client from unknown to actual types but now its giving the type instantiation error. Also my routes are chained and splitted accordingly like this
const routes = [
products,
prices,
discounts,
customers,
addresses,
subscriptions,
transactions,
keys,
// checkout,
] as const;


routes.forEach((route) => {
app.route("/", route);
});
export type AppType = (typeof routes)[number];
const routes = [
products,
prices,
discounts,
customers,
addresses,
subscriptions,
transactions,
keys,
// checkout,
] as const;


routes.forEach((route) => {
app.route("/", route);
});
export type AppType = (typeof routes)[number];
now getting the type error here
import app, { type AppType } from "@repo/api";
import { hc } from "hono/client";
const client = hc<AppType>("");
//^? type error happens here
import app, { type AppType } from "@repo/api";
import { hc } from "hono/client";
const client = hc<AppType>("");
//^? type error happens here
63 Replies
Arjix
Arjix•2mo ago
For typescript to be aware of the routes everything must be chained together in one expression. e.g.
const app = new Hono()
.route("/", products)
.route("/", prices);

export type AppType = typeof app;
const app = new Hono()
.route("/", products)
.route("/", prices);

export type AppType = typeof app;
This is smth the docs fail to mention, and the given example in the docs should not have worked I don't know what sorcery the docs did Also, don't import app directly in your frontend If your project so far doesn't have proprietary code, can you share the code? When I am free I can help structure typescript stuff better, unless you wanna try on your own
ambergristle
ambergristle•2mo ago
which example do you mean?
Arjix
Arjix•2mo ago
For hono client, hc, in the hono stacks guide
ambergristle
ambergristle•2mo ago
damn. no bueno i've been meaning to open a PR to make some docs updates. i can add this to those, unless you'd like to open one for this @Arjix
Arjix
Arjix•2mo ago
Feel free to do the update, unfortunately I lack the free time and energy to do it myself
ambergristle
ambergristle•2mo ago
fair enough. i've got nothing but time, lol
Arjix
Arjix•2mo ago
I guess the docs are correct in the code example, but they are pretty ambiguous I'd say There is an inconsistency in the code style used
ambergristle
ambergristle•2mo ago
@Tony on an unrelated note, it's helpful if you add syntax highlighting to your code snippets. discord supports markdown syntax, so you can just add the language after the opening backticks, e.g., ```typescript
Tony
TonyOP•2mo ago
but i kinda do this already
routes.forEach((route) => {
app.route("/", route);
});
routes.forEach((route) => {
app.route("/", route);
});
ambergristle
ambergristle•2mo ago
no, you're right. i tried it out on my local and the client can't infer
Arjix
Arjix•2mo ago
Typescript can't infer that they are all linked together
ambergristle
ambergristle•2mo ago
^
Tony
TonyOP•2mo ago
shoot, let me give it a try and update u
Arjix
Arjix•2mo ago
Yeah the docs rewrite the backend w/o explaining why, so the example is correct but it doesn't tell you to write your code like that It simply tells you to export it's type
ambergristle
ambergristle•2mo ago
ahhh. you mean
const app = new Hono()
const route = app.get(/** */)

export type AppType = typeof route
// instead of
// export type AppType = typeof app
const app = new Hono()
const route = app.get(/** */)

export type AppType = typeof route
// instead of
// export type AppType = typeof app
yeah. that's sneaky
Arjix
Arjix•2mo ago
Yep Very ambiguous Totally needs clarification Update where
Tony
TonyOP•2mo ago
I have tried on my own but havent managed to get it working. the ts inference and autocomplete are slow af. when u get a min u can check it out maybe https://github.com/BrightonMboya/jani-payments
GitHub
GitHub - BrightonMboya/jani-payments: A Billing Engine where you ge...
A Billing Engine where you get to bring your own Payment Provider. Think of it as Stripe | Paddle | Polar.sh but with Mobile Money support out of the box - BrightonMboya/jani-payments
ambergristle
ambergristle•2mo ago
you may need to chain here too: https://github.com/BrightonMboya/jani-payments/blob/a31f1b1c68ef3fb37512f838ab65673c40af055e/packages/api/src/lib/create-app.ts#L19 and you probably want to be using generated types for the client nmkl\
Tony
TonyOP•2mo ago
so chaining it on 2 places? how do you do that?
ambergristle
ambergristle•2mo ago
typescript only knows what to do with the initialized type of a value.
// typeof app includes all the methods
const app = new Hono()
.get(/** */)
.post(/** */)
.delete(/** */)
// typeof app includes all the methods
const app = new Hono()
.get(/** */)
.post(/** */)
.delete(/** */)
if you split up your method calls, it has no way of knowing you've done that
// typeof app DOES NOT include any methods
const app = new Hono()
app.get(/** */)
app.post(/** */)
app.delete(/** */)
// typeof app DOES NOT include any methods
const app = new Hono()
app.get(/** */)
app.post(/** */)
app.delete(/** */)
you could do this
// typeof app DOES NOT include any methods
const app = new Hono()
// typeof routeA has ONE get method
const routeA = app.get(/** */)
// typeof routeB has ONE get method
const routeB = app.get(/** */)
// typeof app DOES NOT include any methods
const app = new Hono()
// typeof routeA has ONE get method
const routeA = app.get(/** */)
// typeof routeB has ONE get method
const routeB = app.get(/** */)
but the final type of export app is going to be the type at initialization does that make sense? it's not a hono thing, just how typescript works
Tony
TonyOP•2mo ago
not really mate, I am getting confused i am kinda doing that here no? https://github.com/BrightonMboya/jani-payments/blob/a31f1b1c68ef3fb37512f838ab65673c40af055e/packages/api/src/index.ts#L14C1-L22C21 or maybe i am confused with the way i have set things up already
ambergristle
ambergristle•2mo ago
idk, tbh. you have this in your tsconfig already, but you might have to also configure the bundler? i'm not sure, sorry
"declaration": true, // generates the .d.ts files
"declarationDir": "true", // generates a source map for the .d.ts files
"declaration": true, // generates the .d.ts files
"declarationDir": "true", // generates a source map for the .d.ts files
you are there, but not here: https://github.com/BrightonMboya/jani-payments/blob/a31f1b1c68ef3fb37512f838ab65673c40af055e/packages/api/src/lib/create-app.ts#L19 you break the chain at the start
Tony
TonyOP•2mo ago
yes i have this in my tsconfig, what i am not sure is a way of generating the types,
ambergristle
ambergristle•2mo ago
me neither. sorry the short answer RE your typing issue is that you're doing a lot of abstraction for a Hono app. if that's your preference, that's totally fine, but it makes it harder for typescript, and that will cost you performance generated types will help some though i think these docs might be helpful: https://hono.dev/docs/guides/best-practices @Tony this might also be helpful: https://hono.dev/docs/guides/rpc#compile-your-code-before-using-it-recommended
Arjix
Arjix•2mo ago
@Tony typescript types are immutable, that is the fundamental issue here Every time you alter the type (by adding a route) you are creating a new type derived from the previous type Therefore, if you assign routes to your app using a loop, you are not altering the original type of the app Inside the loop you are creating a new temporary type derived from the blank app + the current iteration of the routes
Arjix
Arjix•2mo ago
GitHub
vite-react-ts-hono/src/backend at main · ArjixWasTaken/vite-react-t...
A template based on vite+react+ts with hono as an addition. - ArjixWasTaken/vite-react-ts-hono
Arjix
Arjix•2mo ago
Basically, you need to chain all the routes together in one statement It doesn't mean you can't split your routes into folders, it just means you can't use convenience methods like a loop to dynamically register them
ambergristle
ambergristle•2mo ago
great explanation!
Arjix
Arjix•2mo ago
Thanks, it took me a while to ponder it
Tony
TonyOP•2mo ago
I get this part of chaining the routes. I did that and the initial error of type instansiation is deep and possibly infinte the new issue comes with the rpc inside the client is very slow on type inference and auto complete. it gets the type yes but its slow I am trying the fix of using generated types suggested by ambergristle maybe i am missing sth very obvious which i cant see it yet
Psi
Psi•2mo ago
Pls ping me tomorrow, I share what I've came up with Nutshell, monorepo with workspaces, hono app package json has types property which points to client.d.ts
Tony
TonyOP•2mo ago
alright cool !! thanks a ton
Psi
Psi•2mo ago
/package.json "workspaces": [ "backend", "web-ui" ], /backend/package.json "type": "module", "main": "dist/src/client.js", "typings": "dist/src/client.d.ts", /backend/tsconfig.json composite true /ui/package.json "backend": "workspace:*", /ui/tsconfig.json "references": [ { "path": "../backend" } So in ui package.json we reference the NAME of backend module (name prop of /backend/package.json) which helps ide and bundler to pick up the generated type The typescript project references is only for (re)build if you change backend stuff without explicitly build it In vite config We must use checker({ typescript: { buildMode: true, }, Sry for missing backticks, I'm on the phone right now
Tony
TonyOP•2mo ago
damn it, somehow i keep on getting confused on this. do u mind checking it out on the repo ? or maybe sending u a minimum repo will work for you? basically the main issue rn is the rpc type inference and autocomplete is not fast in the client, basically in this file here https://github.com/BrightonMboya/jani-payments/blob/a31f1b1c68ef3fb37512f838ab65673c40af055e/apps/www/src/server/hono.ts#L6 it gets the types yes, but the automplete part is the one which is super slow
Psi
Psi•2mo ago
Yes it's because you don't compile your types. I will get to it later
Tony
TonyOP•2mo ago
I guess thats the culprit, have tried running this through claude but no luck
ambergristle
ambergristle•2mo ago
Psi
Psi•2mo ago
There is trpc stuff in your repo? And I dont see type path definitions in your package.json
Tony
TonyOP•2mo ago
there was a trpc api on the apps/www package but i slowly migrated it to hono. so u can just ignore the trpc stuff yeah this is what i havent done yet, still confuses me to get the compilation correct I tried this, but it could still be slow on the type inference and auto complete
Psi
Psi•2mo ago
autocomplete in "backend/client.ts" is ALWAYS slow. so it is for the backend in general BUT not in frontend
Tony
TonyOP•2mo ago
so there's no workaround for this?
Psi
Psi•2mo ago
GitHub
GitHub - psi-4ward/hono-rpc-vite-example
Contribute to psi-4ward/hono-rpc-vite-example development by creating an account on GitHub.
Psi
Psi•2mo ago
You do usually not need your own client in the backend dont u? 😄
Tony
TonyOP•2mo ago
yeah the client is meant for the nextjs app
Psi
Psi•2mo ago
Than test your autocomplete here!!
Tony
TonyOP•2mo ago
which i am already, are we on the same page though
ambergristle
ambergristle•2mo ago
there's definitely some miscommunication happening the repo you've shared has a lot going on, including a monorepo manager i haven't seen before, so it's really difficult to isolate the problem also, IDE performance can depend a lot on other factors, including the IDE itself and your device specs there are known issues with Hono + IDE Performance at scale. these are somewhat documented, but there are also threads on this server that have more details sharing a minimal example would be helpful, along with any instructions required to reproduce what you're experiencing
Psi
Psi•2mo ago
Thy can be completly solved be pre-compiling the type and consume ".d.ts" file afaik
ambergristle
ambergristle•2mo ago
depends on how many endpoints you have, and how complicated your typing is at 50-100 endpoints, hono is known to struggle
Psi
Psi•2mo ago
I let AI generate 1000 endpoints each with get/post/put/delete. No problems so far cause it results in (a very large) client.d.ts BUT thy only magic here is the ClientRequest type which is not so hard complicated. tbh, first I liked the way hono handles the types but meantime I changed my mind. defining them explicitly whould make thing much more easy and clear
ambergristle
ambergristle•2mo ago
i can't comment on what the AI generated. i just know that there are a number of open issues RE performance, and that there's some clear albeit limited benchmarkiing you can set the types explicitly if you want. or is that not what you mean?
Psi
Psi•2mo ago
Afaik can I not set the type on route handler to circumvent the hono magic
ambergristle
ambergristle•2mo ago
Imo it’s not ideal, but yeah. If that’s what you prefer, or if you’re having serious performance issues, it’s an option
Psi
Psi•2mo ago
Can you throw a link around or a little example? Afair had @Aditya Mathur a good article about that but did not bookmark it and could not find it anymore 😭
Psi
Psi•2mo ago
I dont understand this. This means 'foo/:id' generic makes things faster?
Arjix
Arjix•2mo ago
Typescript can run DOOM, yet it struggles computing all the routes? (I obviously know that it takes a long time to generate a single frame)
ambergristle
ambergristle•2mo ago
Yeah. You might want to also specify the inputs + outputs using the rest of the generic parameters, but even specifying the path reduces how much inference the server needs to do Hono merges all the route types together, and then exposes them individually in a handler or the rpc. That’s a lot of inference There are clearly some issues with this at scale, and imo it requires some funky patterns (e.g., manually typing middleware, but not handlers)
Arjix
Arjix•2mo ago
You can ignore me, I was partly joking, but it is darn impressive that it can run DOOM
ambergristle
ambergristle•2mo ago
Lmfao. That is pretty insane Is that like a law of computing? Given enough time, someone will try to run DOOM in every execution context
Arjix
Arjix•2mo ago
DOOM + bad apple
Psi
Psi•2mo ago
https://canitrundoom.org/ 😄🥸🤓
Can It Run Doom? An Archive of All Known Ports
Explore an archive of Doom ports showcasing how the game has been adapted to run on various devices, even those not originally intended for gaming.
Aditya Mathur
Aditya Mathur•2mo ago
Now I also wanna know which one was it? 😂

Did you find this page helpful?