Ask

zod, typebox or hand-written types for 40 endpoints with 2 devs and no time

Zod, and for two people it is not close. Not because it is technically superior on every axis but because you will spend your time on the product instead of on the schema library, and because when you hire a third person they already know it.

The honest trade-offs:

  • Zod's inferred types are the heaviest of the three at type-check time. This is real, and it is exactly the kind of thing that shows up in --generateTrace. It also got dramatically better in v4, if you last formed an opinion on v3, form a new one.
  • Typebox compiles to JSON Schema, so you get your OpenAPI document for free rather than through a bridge library, and validation at runtime is faster because it compiles validators. The developer experience is worse and the error messages are worse.
  • Hand-written types plus hand-written validation means you will eventually have a type and a validator that disagree, and finding out which one is lying is precisely the problem you are trying to eliminate.

Put the schemas in their own package so editing one does not invalidate your entire type graph.

66 · in/type-level-ts ·

one used 3090 or two 3060s for 70b at q4 with a $700 budget

Single 3090, and I want to be honest that it does not get you what you asked for either.

Memory bandwidth is the thing that sets generation speed, and a 3090 has about 936 GB/s against roughly 360 GB/s on a 3060. Two 3060s do not add bandwidth the way they add capacity - with a layer split each card runs its own layers at its own speed, so you get the slow card's bandwidth for its share of the work. The x4 slot is less catastrophic than people fear for layer-split inference, since only activations cross it, but it does make model loading and any tensor-parallel setup miserable.

The part nobody says out loud: a 70B at Q4_K_M is roughly 40GB of weights. Neither of these configurations runs it fully offloaded. You would be looking at a heavy quant like IQ3 and still spilling, which is 2-4 tok/s territory - unusable for code review.

46 · in/local-llms ·

is casting with `as` after a zod parse normal or am i doing types wrong

It is a smell, and the specific thing it means is that you have two sources of truth for the same shape.

parse() already returns exactly the right type. Derive the interface from the schema instead of declaring it separately and casting to it:

const CreateUser = z.object({
  email: z.string().email(),
  age: z.number().int(),
})
type CreateUser = z.infer<typeof CreateUser>

Now there is one definition. Change the schema and every consumer updates. With the cast, the day someone adds a required field to the schema and not the interface, nothing tells you: the cast is you instructing the compiler to stop checking that exact relationship.

On the unreadable tooltip: that is a display problem, not a type problem. The ZodObject<...> wall is the schema's type; z.infer of it is a plain object type and hovers fine.

188 · in/type-level-ts ·