io-ts
io-ts copied to clipboard
Question: Compile Performance with Complex Types
I am using io-ts
in a project with a complex data model, and I'm experiencing severe performance issues with the TypeScript compiler. I'm wondering if there is a way to help the compiler out and simplify the types, without sacrificing correctness or copy-pasting code.
Essentially, the data model is a structured definition of form fields and corresponding mathematical computations. The definitions essentially consist of UI properties, calculation metadata, and an array of fields. The fields themselves are a tagged union type of several intersection types; there is a BaseField
and various fields that extend it, each with required and optional properties. I serve these form definitions to my UI over REST, and I'm using io-ts
to validate the schema.
The problem is that the generated type output from io-ts
appears to be right at the edge of what TypeScript can handle. Either that, or I'm doing something stupid and I don't see it. I have reached a complexity level where the TypeScript compiler hangs indefinitely, sucks up all my CPU and makes all my computer fans kick on.
Here is the definition of my CalculatorField type,:
export declare const CalculatorFieldC: t.UnionC<[t.IntersectionC<[t.IntersectionC<[t.TypeC<{
name: t.StringC;
variable: t.StringC;
type: t.StringC;
}>, t.PartialC<{
columns: t.NumberC;
row: t.NumberC;
}>]>, t.TypeC<{
type: t.LiteralC<"integer">;
}>, t.PartialC<{
initial_value: t.RefinementC<t.NumberC>;
placeholder: t.StringC;
}>]>, t.IntersectionC<[t.IntersectionC<[t.IntersectionC<[t.TypeC<{
name: t.StringC;
variable: t.StringC;
type: t.StringC;
}>, t.PartialC<{
columns: t.NumberC;
row: t.NumberC;
}>]>, t.TypeC<{
type: t.LiteralC<"dropdown">;
}>, t.PartialC<{
initial_value: t.StringC;
}>]>, t.TypeC<{
options: t.ArrayC<t.StringC>;
}>, t.PartialC<{
groups: t.RecordC<t.StringC, t.ArrayC<t.StringC>>;
}>]>, t.IntersectionC<[t.IntersectionC<[t.IntersectionC<[t.TypeC<{
name: t.StringC;
variable: t.StringC;
type: t.StringC;
}>, t.PartialC<{
columns: t.NumberC;
row: t.NumberC;
}>]>, t.TypeC<{
type: t.LiteralC<"dropdown-or-custom">;
custom_input_option: t.StringC;
custom_input_field: t.IntersectionC<[t.IntersectionC<[t.TypeC<{
name: t.StringC;
variable: t.StringC;
type: t.StringC;
}>, t.PartialC<{
columns: t.NumberC;
row: t.NumberC;
}>]>, t.TypeC<{
type: t.LiteralC<"integer">;
}>, t.PartialC<{
initial_value: t.RefinementC<t.NumberC>;
placeholder: t.StringC;
}>]>;
}>, t.PartialC<{
initial_value: t.StringC;
}>]>, t.TypeC<{
options: t.ArrayC<t.StringC>;
}>]>, t.IntersectionC<[t.IntersectionC<[t.TypeC<{
name: t.StringC;
variable: t.StringC;
type: t.StringC;
}>, t.PartialC<{
columns: t.NumberC;
row: t.NumberC;
}>]>, t.TypeC<{
type: t.LiteralC<"origin-destination">;
}>]>]>;
Obviously, that's not a handwritten definition, it's the composed result of the various component types.
This type itself does not have problems. However, in my UI, I use the same type to represent the state of the form in the UI. The UI state is an intersection of the Field union type and a value
, which is a union (string | number | null
).
Defining the following type causes VSCode to stop processing types and causes webpack to hang indefinitely:
const CalculatorFieldStateC = t.intersection([
CalculatorFieldC,
t.type({
value: t.union([t.string, t.number, t.null])
})
])
Any ideas? Am I doing something stupid and causing an infinite loop without seeing it? Is there a way to collapse these types, without declaring the full type of every Field by hand?
I can workaround the problem in the UI; I was only defining the above codec to validate state after rehydrating it from browser storage (since the structure is likely to change between versions of the app). However, the complexity of these fields is only going to increase as the app grows, with more field types, etc.
The structure stored in the database actually extends from the above Field type, because the database contains coefficients and other information that the server uses to perform the calculations that the UI doesn't see. It would be an absolute disaster to declare all of the necessary types separately.
I'd appreciate any help.
Hmm. I don't fully understand what's going on, but somehow the problem is actually related to npm link
, possibly something to do with TypeScript trying to operate with types included over symbolic link. The problem goes away if I npm install
the latest version of my package containing the types.
I'd still like a suggestion to simplify the types if one exists. But I have a workaround for now (local file path dependency instead of npm link
... and here I thought npm link
had finally become useful)
huh interesting, i'm also experiencing similar problems 10 months later. maybe i can try doing what you did and not rely on symlinks? but that would be a bummer if that's the case.