#Type inference from conditionals

29 messages · Page 1 of 1 (latest)

graceful verge
#

Hello, I am trying to parse my env schema to make sure it is valid. Currently I am checking if the window is undefined and parsing the correct schemas accordingly. The problem is that the env variable is always being set to the client types even when on the server.

In the below code env always ends up as only the clientEnv types.

const parsed =
  typeof window === 'undefined'
    ? safeParse(merge([serverEnv, clientEnv]), process.env)
    : safeParse(clientEnv, destructuredEnv)

if (!parsed.success) {
  console.error(
    `Invalid environment variables:`,
    Object.entries(flatten(parsed.issues).nested)
      .map((issue, i) => `${i == 0 ? '\n-- ' : ''}${issue[0]}: ${issue[1]![0]}`)
      .join('\n-- ')
  )
  throw new Error('Invalid environment variables!')
}

export const env = parsed.output
nimble hedge
#

If you don't mind using a library, I would check outhttps://env.t3.gg/docs/introduction

Env

Never build your apps with invalid environment variables again. Validate and transform your environment with the full power of Zod.

#

It solves this problem I think

graceful verge
vivid summit
#

I think the root issue here is that TS sometimes reduces types to a common ancestor type.

#

e.g.

// the type of this is likely `Animal` - the bit where it can be a `Cat` is lost
const catOrAnimal = someCondition ? getCat() : getAnimal();
graceful verge
#

Ah I see, so could I just force the correct type by using type conditionals and casting? or maybe instanceof?

vivid summit
#

A type annotation alone should work

#
const catOrAnimal: Animal | Cat = someCondition ? getCat() : getAnimal();
#

Though the raw union might be a bit annoying to use in practice? You could consider something like:

type Config = ClientConfig & Partial<ServerConfig>
graceful verge
#

Got it and thank you for the help! I will mess around this this then.

graceful verge
#

In an ideal world I would be able to only see the client types on the client and all types on the server with all types exactly matching my schemas (no possible undefined).

vivid summit
nimble hedge
#

you should look into the code for env.t3 and just copy it

#

it should be straightforward to swap out zod for valibot

vivid summit
#

I don't think env.t3 actually solves the core issue they're describing here.

nimble hedge
#

well iirc the issue would be sidestepped because you explicitly separate the client and server env vars

#

and they would end up having different types

vivid summit
#

Yeah, and some sort of separation is probably the right idea but you don't need a library for that. I'd maybe do something like:

const getClientEnv = () => {
    return safeParse(clientEnv, destructuredEnv)
}

const getServerEnv = () => {
   if(typeof window !== 'undefined') throw new Error("Attempted to read server config from the client");
   return safeParse(merge([serverEnv, clientEnv]), process.env)
}
#

It'd depend on how these are meant to be used, I guess.

nimble hedge
#

valibot integration ><

graceful verge
nimble hedge
#

yeah, t3env does something similar to what retsam is doing above

#

but its per env var that doesn't have a CLIENT_ prefix

#

and it uses a getter so it has better DX