Zod is amazing at performing crazy type transformations and provide an inferred result that is completely devoid of generic types when previewed in your IDE.
const ObjSchema = z.object({
t: z.enum(['A', 'B'])
}).transform(obj => {
return obj.t === 'A' ? { bar: true } : { baz: 'bar' }
})
type ObjSchemaInput = z.input<typeof ObjSchema>
// { t: 'A' | 'B' }
type ObjSchemaOutput = z.infer<typeof ObjSchema>
/* {
bar: boolean;
baz?: undefined;
} | {
baz: string;
bar?: undefined;
} */
When trying to do my own complex type logic, I get much less useful previews.
Take this stupid example:
type Bar = { bar: true }
type Baz<T> = { baz: T extends 'A' ? 'foo' : 'bar' }
type Foo<T extends 'A' | 'B'> = {
[K in 'A' | 'B']: T extends 'A' ? Bar : Baz<T>
}
type T1 = Foo<'B'> // { A: Baz<'B'>, B: Baz<'B'> }
There I expect T1
to be reported as { A: { baz: 'foo' }, B: { baz: 'foo' } }
While looking complex, these types are shallow and finite, and should be easily resolvable.
But this nonsense works great:
type Obj<U extends string, V extends string> = {
uv: U extends 'a' ? `${U}-${V}` : never
}
type Foo<T extends string> =
T extends `${infer U}:${infer V}` ? Obj<U, V> : never
type A = Foo<'a:b'> // { uv: 'a-b' }
Based on the first example, I'd expect that to say like Obj<'a', 'b'>
, but here it does the right thing and I'm not entirely sure why.
So how do I get type reporting more like Zod?
Motivating Example
I don't expect anyone to debug this, but it illustrates the problem better.
I'm trying to provide types for setter functions of GLSL shaders derived from only the source code.
For example:
const vertex = /* glsl */ `
attribute vec3 position;
attribute vec2 normal;
uniform float value;
uniform vec2 vec2Value;
varying vec2 uv;
void main() {
gl_Position = vec4(position, 1.0);
uv = position.xy;
}
`
declare const shaderObject: ShaderObject<typeof vertex>
shaderObject.uniforms.vec2Value.set(1, 2)
// ^?
My types so far return a type hint at that location that looks like this:
(property) uniforms: Settables<"uniform", SettableVariableDeclarations<[SingleDeclaration<"vec3", "attribute", "position">, SingleDeclaration<"vec2", "attribute", "normal">, SingleDeclaration<"float", "uniform", "value">, SingleDeclaration<...>, SingleDeclaration<...>, SingleDeclaration<...>, FunctionDefinition<...>], "uniform">>
Which is nearly useless.
I was hoping for something like:
{
value: { set(n: number): void }
vec2Value: { set(x: number, y: number): void }
}
Which does get more clear as you drill in, and at this location:
shaderObject.uniforms.vec2Value.set(1, 2)
// ^?
The reported type is:
(property) set: (x: number, y: number) => void
Which is great, but you have to discover the properties through autocomplete first, and can't easily see a list of the available props.
So is there some sort of trick to writing your types in such a way that resolves them fully when reported back to you?