Slow types: Better detection/fallback for complex constant declarations
I often use a pattern in my code where some object has a lot of keys, and I want TypeScript to automatically detect the keys and make them part of the type.
For example, in quantity-math-js:
/** SI prefixes */
export const prefixes = Object.freeze(
{
...
k: 1e+3, // kilo
M: 1e+6, // Mega
G: 1e+9, // Giga
..
} as const satisfies Record<string, number>,
);
I want for example to use that to define a related type using all the keys:
Here are two other examples of this pattern with even longer type definitions (more keys):
| A second example: units.ts | A third example: Icon.tsx |
|---|---|
Now, not surprisingly, deno lint and deno publish complain that these are "slow types". But given how complex the types are (see examples), I don't want to duplicate them in the code. And given how useful it is to have the keys available in my IDE with auto-completion, I don't want to simplify the types either.
I think a nice compromise solution would be to degrade these types to whatever type is specified using the satisfies operator, when necessary. So if I slightly re-write my declaration as follows:
export const prefixes = Object.freeze(
{
...
k: 1e+3, // kilo
M: 1e+6, // Mega
G: 1e+9, // Giga
..
} as const,
) satisfies Record<string, number>;
Then ideally, deno and typescript and other first-class users of the code will have the full, detailed type with all the exact keys available, but for JSR's purposes of documenting this and transpiling it for Node.js or whatever other things need to avoid slow types, it should just simplify the type of this to Record<string, number> which is OK to do because of the explicitsatisfies declaration
Would that be possible?
satisfies can't be used, because the resolved type of that is:
const prefixes: Readonly<{
readonly k: 1000;
readonly M: 1000000;
readonly G: 1000000000;
}>
That said, I think it could be improved to understand this scenario.
OK, well if it can just understand the full type, that's even better :) I just want to avoid having to duplicate the type and maintain the list of valid keys in two or more different places.
The same problem occurs when using zod schemas. They are entirely built on inference and a static defintion completely undermines their purpose.
My current workaround:
- Change the zod import to
import {z} from "zod" $ yarn add zod$ tsc --emitDeclarationOnly --declaration --target esnext --moduleresolution nodenext --module nodenext schema.ts- Copy-paste the declaration to schema.ts and use it as static type
- Delete all generated files and change back the import
Without knowing anything about the internal structure of jsr, I would suggest infering all slow types and automatically inserting them at publish time might solve the problem. This would also help to avoid class members like this: readonly foo: Map<string,number> = new Map()