Skip to content
This repository has been archived by the owner on Sep 27, 2023. It is now read-only.

%future added value #203

Open
renanmav opened this issue Aug 23, 2020 · 3 comments
Open

%future added value #203

renanmav opened this issue Aug 23, 2020 · 3 comments
Labels
question Further information is requested

Comments

@renanmav
Copy link
Contributor

renanmav commented Aug 23, 2020

Why do we have this?

if (!noFutureProofEnums) {
values.push("%future added value");
}

@renanmav renanmav added the question Further information is requested label Aug 23, 2020
@kassens
Copy link

kassens commented Sep 24, 2020

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.

@0x450x6c
Copy link

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.

@0x450x6c
Copy link

0x450x6c commented Oct 28, 2021

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants