Open hisamafahri opened 1 year ago
Hi @hisamafahri !
Thanks for raising the issue.
You are right! Unfortunately, I only know the way that uses Union
to export route types as you wrote. I am looking for a smarter way to export multiple routes, too.
For now, let's leave this Issue open.
Chaining produces better results. app = new Hono().get().get()
. However, it falls apart when using .route()
to bundle endpoints and middleware. Especially if you do .route('/:userId', new Hono().get('/key/:keyId'))
. Using validator()
ware also causes different results (at least within .route()
calls).
See the tests for what's currently supported.
any update on here?
@andreataglia
No. Please use Union
:
export type ApiType = typeof helloRoute | typeof greetRoute;
if write that(not chaining), the app type doesn't work;
const app = new Hono()
app.use();
app.use();
export AppType = typeof app
// ...
you want type-safe, only use chaining style, but the code style likes tRPC;
// hono style
const app = new Hono().use().use() // ...
// tRPC style
const route = t.route({
list: () => {}
//...
})
I think, Have any plan which can generate route type(or other app's meta) for client(or plugin) in build time? so we can choice any free style to write routes and keep type-safe.
Have any plan which can generate route type(or other app's meta) for client(or plugin) in build time?
We currently don't have a plan for this, but if you know of any good example applications that can generate route types statically at build time, please let us know. We can refer to it.
I attempted to write a helper class to assist in collecting type information. While I think it's better than manually writing union types, it still feels a bit awkward. I hope someone can propose a better idea.
ApiSpec class
export class ApiSpec<T=null> {
spec = ((o: T) => {
return o!;
})({} as T)
define<NT>(_: NT): ApiSpec<T | NT> {
return new ApiSpec<T | NT>();
}
}
Usage
const app = new Hono();
const apiSpec = new ApiSpec()
.define(app.post('/foo', ...
(c) => {
...
}
))
.define(app.post('/bar', ...
(c) => {
...
}
))
export type AppType = typeof apiSpec.spec;
While this ticket is being considered, I eventually shifted to tRPC as an option which works nicely with:
// or use mergeRouters()
export const appRouter = router({
...originRouter(),
...franchiseRouter()
})
export type AppRouter = typeof appRouter
Sample:
@server/trpcs.ts
import { initTRPC } from '@trpc/server'
const t = initTRPC.create()
export const router = t.router
export const publicProcedure = t.procedure
@server/router.ts
import { router } from '@server/trpc'
import { originRouter } from '@server/modules/origins'
import { franchiseRouter } from '@server/modules/franchises'
export const appRouter = router({
...originRouter(),
...franchiseRouter()
})
export type AppRouter = typeof appRouter
@server/main.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { trpcServer } from '@hono/trpc-server' // Deno 'npm:@hono/trpc-server'
import { appRouter } from './router'
const app = new Hono()
app.use(cors())
app.use(
'/trpc/*',
trpcServer({
router: appRouter
})
)
export default app
@server/modules/origins/index.ts
import { z } from 'zod'
import { publicProcedure, router } from '@server/trpc'
import { faker } from '@faker-js/faker'
import { v4 as uuid } from 'uuid'
import { TRPCError } from '@trpc/server'
import { on } from 'events'
import { eventEmitter } from '@server/events'
import { CREATE_ORIGIN } from '@server/modules/origins/types'
const OriginSchema = z.object({
id: z.string().uuid(),
name: z.string().max(64),
year: z.number().int().min(1900).max(new Date().getFullYear()),
updatedAt: z.string()
})
export type Origin = z.infer<typeof OriginSchema>
const data: Array<Origin> = [
{
id: uuid(),
name: faker.company.name(),
year: faker.date.past({ years: 1 }).getFullYear(),
updatedAt: new Date().toISOString()
}
]
const CreateOriginSchema = OriginSchema.omit({ id: true, updatedAt: true })
export type CreateOriginDto = z.infer<typeof CreateOriginSchema>
const getSchema = z.object({
id: z.string().uuid()
})
export const originRouter = () => {
return {
origins: router({
create: publicProcedure
.input(CreateOriginSchema)
.mutation(async ({ input }) => {
const origin: Origin = OriginSchema.parse({
id: uuid(),
name: input.name,
year: input.year,
updatedAt: new Date().toISOString()
}) satisfies Origin
data.push(origin)
eventEmitter.emit(CREATE_ORIGIN, origin)
return origin
}),
getAll: publicProcedure.query(() => {
return data
}),
get: publicProcedure.input(getSchema).query(async ({ input }) => {
const origin = data.find((origin) => origin.id === input.id)
if (origin == null) {
throw new TRPCError({
code: 'NOT_FOUND'
})
}
return origin
}),
onCreate: publicProcedure.subscription(async function* () {
for await (const [data] of on(eventEmitter, CREATE_ORIGIN)) {
yield data as Origin
}
})
})
}
}
Hi, thank you for the amazing work for HonoJS so far.
I have a question, how do I export types for multiple route using one
export
declaration? Because I'd like to import single type in my client (const client = hc<ApiType>("/api")
).For example:
Currently my workaround is this:
But this approach requires me to add another union declaration for each routes that I have.