Ego
Ego
Explore posts from servers
HHono
Created by Ego on 4/16/2025 in #help
Handler type
The complex types in this specific use case do feel perfect for my style
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Yeah I can see that
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
That resource has 4 or 5 methods that might or might not slightly change the way the schema looks
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Makes sense if they're not owned by a specific resource
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
That makes sense as long as you don't share a lot of code like schemas etc
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
It feels awkward, I come from a Laravel background and I really like how it handles most stuff
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Yeah that would simplify my life a lot but at the same time I personally don't like how the approach looks
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
If I did use a new Hono on each file etc it could work but then separation of concerns would be an annoying thing
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Unfortunately that would clutter my files incredibly
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
That would work if I defined my route in the function itself
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
I'm pretty sure I could use hono's own handler type if I knew more but I'm sadly not smart enough
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
That's why I made the createRoute factory to try and be as plug and play as possible
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
That's exactly what I'm using it just handles things way different and doesn't provide generic types for this
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Otherwise it would work perfectly
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
It's just a bit sad that the official zod-openapi middleware uses a package underneath that is abandonware
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Yeah I'm pretty sure I should be using the hono types directly, tbh I'm just a bit clueless and this is actually way too complicated for me to start with
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
Always ends up in a never
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
It's not inferring the return c.json
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
I have come up with this but it feels incredibly incorrect and honestly kinda disgusting and it still doesn't do the return types correctly
import type { Context, Env } from "hono";
import type { RouteOutput } from "./routing";
import type { z, ZodType } from "zod";

type MaybePromise<T> = T | Promise<T>;

type ExtractZodSchema<T> = T extends ZodType ? T : never;

type ValidKeys = "query" | "param" | "header" | "json" | "form";

type InputFromRequest<Request> = {
param: Request extends { params: infer P }
? z.output<ExtractZodSchema<P>>
: never;
query: Request extends { query: infer Q }
? z.output<ExtractZodSchema<Q>>
: never;
header: Request extends { headers: infer H }
? z.output<ExtractZodSchema<H>>
: never;
json: Request extends {
body: { content: { "application/json": { schema: infer Schema } } };
}
? z.output<ExtractZodSchema<Schema>>
: never;
form: Request extends {
body: { content: { "multipart/form-data": { schema: infer Schema } } };
}
? z.output<ExtractZodSchema<Schema>>
: never;
};

type MergeInput<I extends Record<string, any>> = {
[K in keyof I]: I[K];
};

type ExtractResponseContent<R> = R extends { responses: infer Responses }
? {
[K in keyof Responses]: Responses[K] extends { content: infer Content }
? Content
: never;
}[keyof Responses]
: never;

type ValidatedContext<
E extends Env,
I extends Record<string, any>,
> = Context<E> & {
req: {
valid: <K extends ValidKeys>(key: K) => I[K];
};
};

export type RouteHandler<
R extends RouteOutput<any, any, any, any, any, any>,
E extends Env = Env,
> = (
c: ValidatedContext<E, MergeInput<InputFromRequest<R["request"]>>>,
) => MaybePromise<ExtractResponseContent<R["description"]>>;
import type { Context, Env } from "hono";
import type { RouteOutput } from "./routing";
import type { z, ZodType } from "zod";

type MaybePromise<T> = T | Promise<T>;

type ExtractZodSchema<T> = T extends ZodType ? T : never;

type ValidKeys = "query" | "param" | "header" | "json" | "form";

type InputFromRequest<Request> = {
param: Request extends { params: infer P }
? z.output<ExtractZodSchema<P>>
: never;
query: Request extends { query: infer Q }
? z.output<ExtractZodSchema<Q>>
: never;
header: Request extends { headers: infer H }
? z.output<ExtractZodSchema<H>>
: never;
json: Request extends {
body: { content: { "application/json": { schema: infer Schema } } };
}
? z.output<ExtractZodSchema<Schema>>
: never;
form: Request extends {
body: { content: { "multipart/form-data": { schema: infer Schema } } };
}
? z.output<ExtractZodSchema<Schema>>
: never;
};

type MergeInput<I extends Record<string, any>> = {
[K in keyof I]: I[K];
};

type ExtractResponseContent<R> = R extends { responses: infer Responses }
? {
[K in keyof Responses]: Responses[K] extends { content: infer Content }
? Content
: never;
}[keyof Responses]
: never;

type ValidatedContext<
E extends Env,
I extends Record<string, any>,
> = Context<E> & {
req: {
valid: <K extends ValidKeys>(key: K) => I[K];
};
};

export type RouteHandler<
R extends RouteOutput<any, any, any, any, any, any>,
E extends Env = Env,
> = (
c: ValidatedContext<E, MergeInput<InputFromRequest<R["request"]>>>,
) => MaybePromise<ExtractResponseContent<R["description"]>>;
57 replies
HHono
Created by Ego on 4/16/2025 in #help
Handler type
My problem could be simplified quite a bit if I wasn't using the speerate definitions.ts and handlers.ts files and I would just register stuff right away tho it would make my code way less readable tbh so I end up with stuff like this
export const index = createRoute({
method: 'GET',
path: 'items',
tags: ['Items'],
operationId: 'listItems',
request: {
query: PaginationQuery.extend({
orderBy: z
.optional(z.string().pipe(z.enum(['-createdAt', 'createdAt', '-updatedAt', 'updatedAt'])))
.default('-updatedAt'),
}).merge(filterSchema),
},
responses: {
200: {
content: {
'application/json': {
schema: resolver(getPaginatedSchema(selectItemsSchema)),
},
},
description: 'List of items logged in the database',
},
},
});
export const index = createRoute({
method: 'GET',
path: 'items',
tags: ['Items'],
operationId: 'listItems',
request: {
query: PaginationQuery.extend({
orderBy: z
.optional(z.string().pipe(z.enum(['-createdAt', 'createdAt', '-updatedAt', 'updatedAt'])))
.default('-updatedAt'),
}).merge(filterSchema),
},
responses: {
200: {
content: {
'application/json': {
schema: resolver(getPaginatedSchema(selectItemsSchema)),
},
},
description: 'List of items logged in the database',
},
},
});
export const show: RouteHandler<typeof ShowType> = async (c) => {
const params = c.req.valid("param");

const [{ amount }] = await c.var.db
.select({
amount: sql`GREATEST(SUM(${schema.item_logs.amount}), 0)`.mapWith(Number),
})
.from(schema.item_logs)
.where(eq(schema.item_logs.itemId, params.item.id));

return c.json({ ...params.item, amount }, 200);
};
export const show: RouteHandler<typeof ShowType> = async (c) => {
const params = c.req.valid("param");

const [{ amount }] = await c.var.db
.select({
amount: sql`GREATEST(SUM(${schema.item_logs.amount}), 0)`.mapWith(Number),
})
.from(schema.item_logs)
.where(eq(schema.item_logs.itemId, params.item.id));

return c.json({ ...params.item, amount }, 200);
};
57 replies