Open renanmav opened 4 years ago
This was copied from the Flow type definitions, I think the same applies to TypeScript though:
If you used Relay as part of React Native for example, you might have some client deployed and don't want to force-update that client.
At least Flow supports "exhaustiveness checks" where the type checker knows that one branch of a switch
will match.
If your server now adds another enum value in the future (this is not considered a breaking change per GraphQL spec) the old client would somehow have to deal with this newly added value. This value is reflecting this presumed value added in the future do force you to add a default
case to the switch statement or similar.
If you used Relay as part of React Native for example, you might have some client deployed and don't want to force-update that client.
Fair point, but when we provide default
case - we lose the exhaustive checks, compiler will not notify us when schema changes.
Probably, better approach is to keep these types, and change enum value to the %future added value
in case of unexpected values.
I am using following helper function for ts-pattern:
import * as tsPattern from "ts-pattern";
import { typeFest } from "./type-fest";
// eslint-disable-next-line
export type RelayFutureAddedValue = "%other" | "%future added value";
export type UnionToTuple<T> = typeFest.UnionToIntersection<
T extends never ? never : (t: T) => T
> extends (_: never) => infer W
? readonly [...UnionToTuple<Exclude<T, W>>, W]
: readonly [];
/**
* Matches unexpected value by union type.
*
* This way, value that does not match `possibleValues` works as default branch.
*
* It is useful with relay's `%other` and `%future added value` (see https://github.com/relay-tools/relay-compiler-language-typescript/issues/203#issuecomment-698443972).
*/
export const whenUnexpectedUnion = <T extends string>(
possibleValues: UnionToTuple<Exclude<T, RelayFutureAddedValue>>,
) =>
tsPattern.when(
(value: string): value is RelayFutureAddedValue =>
!(possibleValues as readonly string[]).includes(value),
);
Example usage of helper function in react app:
tsPattern
.match(error)
.with(whenUnexpectedUnion<typeof error>(["WRONG_CREDENTIALS"]), () => (
<div key="unexpected" className={styles.generalError}>Something went wrong</div>
))
.with("WRONG_CREDENTIALS", (error) => (
<div key={error} className={styles.generalError}>
Wrong credentials.
</div>
))
.exhaustive()
This way, unexpected value handled correctly without providing default branch, so typescript will inform us when schema changed.
Why do we have this?
https://github.com/relay-tools/relay-compiler-language-typescript/blob/4134988f48d8522cc2403c2da748a428ddebeea5/src/TypeScriptGenerator.ts#L1061-L1063