Skip to content

Commit

Permalink
Using opaque type trick to enforce nested relay entry point safety
Browse files Browse the repository at this point in the history
Reviewed By: josephsavona

Differential Revision: D45799755

fbshipit-source-id: c2e49731319e1250746e7cb858d49f56d9993ae7
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed May 31, 2023
1 parent ce74006 commit de6594b
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 18 deletions.
22 changes: 9 additions & 13 deletions packages/react-relay/relay-hooks/EntryPointTypes.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ defined during component runtime
TExtraProps - a bag of extra props that you may define in `entrypoint` file
and they will be passed to the EntryPointComponent as `extraProps`
*/
type InternalEntryPointRepresentation<
export type InternalEntryPointRepresentation<
TEntryPointParams,
TPreloadedQueries,
TPreloadedEntryPoints,
Expand Down Expand Up @@ -238,24 +238,20 @@ export type ThinQueryParams<
variables: TQuery['variables'],
}>;

type ThinNestedEntryPointParams<TEntryPointParams, TEntryPoint> = $ReadOnly<{
entryPoint: TEntryPoint,
entryPointParams: TEntryPointParams,
}>;
/**
* We make the type of `ThinNestedEntryPointParams` opaque, so that the only way
* to construct a `ThinNestedEntryPointParams` is by calling `NestedRelayEntryPoint`
* from `NestedRelayEntryPointBuilderUtils` module.
*/
declare export opaque type ThinNestedEntryPointParams;

export type ExtractQueryTypeHelper<TEnvironmentProviderOptions> = <TQuery>(
PreloadedQuery<TQuery>,
) => ThinQueryParams<TQuery, TEnvironmentProviderOptions>;

export type ExtractEntryPointTypeHelper = <
TEntryPointParams,
TEntryPointComponent,
>(
export type ExtractEntryPointTypeHelper = <TEntryPointComponent>(
?PreloadedEntryPoint<TEntryPointComponent>,
) => ?ThinNestedEntryPointParams<
TEntryPointParams,
EntryPoint<TEntryPointParams, TEntryPointComponent>,
>;
) => ?ThinNestedEntryPointParams;

export type EntryPoint<TEntryPointParams, +TEntryPointComponent> =
InternalEntryPointRepresentation<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall relay
*/

'use strict';

import type {
InternalEntryPointRepresentation,
ThinNestedEntryPointParams,
} from './EntryPointTypes.flow';

/**
* This is an identity function to construct a type safe nested entrypoint.
* By calling this function, we ensure that the type of entryPointParams matches
* exactly the type of preloadProps of the entrypoint.
*
* We make the type of `ThinNestedEntryPointParams` opaque, so that the only way
* to construct a `ThinNestedEntryPointParams` is by calling this function.
*/
declare function NestedRelayEntryPoint<
TEntryPointParams,
TPreloadedQueries,
TPreloadedEntryPoints,
TRuntimeProps,
TExtraProps,
>(
$ReadOnly<{
entryPoint: InternalEntryPointRepresentation<
TEntryPointParams,
TPreloadedQueries,
TPreloadedEntryPoints,
TRuntimeProps,
TExtraProps,
>,
entryPointParams: TEntryPointParams,
}>,
): ThinNestedEntryPointParams;

// eslint-disable-next-line no-redeclare
function NestedRelayEntryPoint<P>(params: P): P {
return params;
}

export {NestedRelayEntryPoint};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
} from '../../EntryPointTypes.flow';
import type {JSResourceReference} from 'JSResourceReference';

import {NestedRelayEntryPoint} from '../../NestedRelayEntryPointBuilderUtils';

declare function mockJSResource<TModule>(
module: TModule,
): JSResourceReference<TModule>;
Expand Down Expand Up @@ -65,14 +67,14 @@ type BadParentEntrypointParams = $ReadOnly<{}>;
getPreloadProps(_params: BadParentEntrypointParams) {
return {
entryPoints: {
nestedComponent: {
entryPoint: NestedEntryPoint,
nestedComponent: NestedRelayEntryPoint({
/**
$FlowExpectedError The entryPointParams here should be of type
NestedEntrypointPreloadParams, but it does not contain subEntrypointPreloadParam
*/
entryPoint: NestedEntryPoint,
entryPointParams: Object.freeze({}),
},
}),
},
};
},
Expand All @@ -90,13 +92,13 @@ type GoodParentEntrypointParams = $ReadOnly<{}>;
getPreloadProps(_params: GoodParentEntrypointParams) {
return {
entryPoints: {
nestedComponent: {
nestedComponent: NestedRelayEntryPoint({
entryPoint: NestedEntryPoint,
// No flow error since this matches NestedEntrypointPreloadParams
entryPointParams: {
subEntrypointPreloadParam: 'test',
},
},
}),
},
};
},
Expand Down

0 comments on commit de6594b

Please sign in to comment.