diff --git a/package.json b/package.json index 3174eba55..d0300fe1c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,9 @@ "@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-replace": "^2.3.2", - "@types/react": ">=16.13.1", + "@types/react": ">=18.0.9", + "@types/react-relay": ">=13.0.2", + "@types/relay-runtime": ">=13.0.3", "babel-jest": "^26.0.1", "babel-plugin-module-resolver": "^4.0.0", "babel-plugin-relay": "^13.2.0", diff --git a/typescript/index.d.ts b/typescript/index.d.ts index 3079b601e..0462e5e26 100644 --- a/typescript/index.d.ts +++ b/typescript/index.d.ts @@ -1,4 +1,4 @@ -// Minimum TypeScript Version: 3.7 +// Minimum TypeScript Version: 3.9 /** * Copyright (c) Facebook, Inc. and its affiliates. diff --git a/typescript/recoil-relay-test.ts b/typescript/recoil-relay-test.ts new file mode 100644 index 000000000..c1e8c9a60 --- /dev/null +++ b/typescript/recoil-relay-test.ts @@ -0,0 +1,260 @@ +// Minimum TypeScript Version: 3.9 + +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+recoil + */ + + import React = require('react'); + import { atom } from 'recoil'; + import { + EnvironmentKey, + RecoilRelayEnvironment, + RecoilRelayEnvironmentProvider, + graphQLQueryEffect, + graphQLSubscriptionEffect, + graphQLMutationEffect, + graphQLSelector, + graphQLSelectorFamily, + } from 'recoil-relay'; + import { IEnvironment, graphql } from 'relay-runtime'; + import { useRelayEnvironment } from 'react-relay'; + + // Environment + const myEnv: IEnvironment = useRelayEnvironment(); + + // EnvironmentKey + const myEnvKey: EnvironmentKey = new EnvironmentKey('test'); + + // + RecoilRelayEnvironment({ // $ExpectType ReactElement | null + children: React.createElement('div'), + environment: myEnv, + environmentKey: myEnvKey, + }); + RecoilRelayEnvironment({ // $ExpectType ReactElement | null + children: React.createElement('div'), + environment: myEnv, + environmentKey: myEnvKey, + extraArg: 'ERROR', // $ExpectError + }); + RecoilRelayEnvironment({ // $ExpectError + children: React.createElement('div'), + environment: myEnv, + }); + + // + RecoilRelayEnvironmentProvider({ // $ExpectType ReactElement | null + children: React.createElement('div'), + environment: myEnv, + environmentKey: myEnvKey, + }); + RecoilRelayEnvironmentProvider({ // $ExpectType ReactElement | null + children: React.createElement('div'), + environment: myEnv, + environmentKey: myEnvKey, + extraArg: 'ERROR', // $ExpectError + }); + RecoilRelayEnvironmentProvider({ // $ExpectError + children: React.createElement('div'), + environment: myEnv, + }); + + // graphQLQueryEffect() + atom({ + key: 'key', + effects: [graphQLQueryEffect({ + environment: myEnv, + query: graphql`query...`, + variables: {foo: 'bar'}, + mapResponse: (data: {str: string}) => data.str, + })], + }); + atom({ + key: 'key', + effects: [graphQLQueryEffect({ + environment: myEnvKey, + query: graphql`query...`, + variables: null, + mapResponse: (data: {str: string}) => data.str, + })], + }); + atom({ + key: 'key', + effects: [graphQLQueryEffect({ // $ExpectError + environment: myEnv, + query: graphql`query...`, + variables: {foo: 'bar'}, + mapResponse: (data: {str: string}) => data, + })], + }); + + // graphQLSubscriptionEffect() + atom({ + key: 'key', + effects: [graphQLSubscriptionEffect({ + environment: myEnv, + subscription: graphql`subscription...`, + variables: {foo: 'bar'}, + mapResponse: (data: {str: string}) => data.str, + })], + }); + atom({ + key: 'key', + effects: [graphQLSubscriptionEffect({ + environment: myEnvKey, + subscription: graphql`subscription...`, + variables: null, + mapResponse: (data: {str: string}) => data.str, + })], + }); + atom({ + key: 'key', + effects: [graphQLSubscriptionEffect({ // $ExpectError + environment: myEnv, + subscription: graphql`subscription...`, + variables: {foo: 'bar'}, + mapResponse: (data: {str: string}) => data, + })], + }); + + // graphQLMutationEffect() + atom({ + key: 'key', + effects: [graphQLMutationEffect({ + environment: myEnv, + mutation: graphql`mutation...`, + variables: str => ({foo: str}), + })], + }); + atom({ + key: 'key', + effects: [graphQLMutationEffect({ + environment: myEnvKey, + mutation: graphql`mutation...`, + variables: () => null, + })], + }); + atom({ + key: 'key', + effects: [graphQLMutationEffect({ + environment: myEnv, + mutation: graphql`mutation...`, + variables: str => ({foo: str}), + updater_UNSTABLE: (store, data) => { + store; // $ExpectType RecordSourceSelectorProxy<{ bar: string; }> + data; // $ExpectType { bar: string; } + }, + optimisticUpdater_UNSTABLE: (store, data) => { + store; // $ExpectType RecordSourceSelectorProxy<{ bar: string; }> + data; // $ExpectType { bar: string; } + }, + optimisticResponse_UNSTABLE: str => ({bar: str}), + })], + }); + + // graphQLSelector() + graphQLSelector<{foo: string}, string>({ // $ExpectType RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + }); + graphQLSelector<{foo: string}, string>({ // $ExpectType RecoilState + key: 'key', + environment: myEnvKey, + query: graphql`query...`, + variables: ({get}) => { + get; // $ExpectType GetRecoilValue + return null; + }, + mapResponse: data => data.str, + }); + graphQLSelector<{foo: string}, string>({ // $ExpectType RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + mutations: { + mutation: graphql`mutation...`, + variables: (str: string) => ({eggs: str}), + }, + }); + graphQLSelector<{foo: string}, string>({ // $ExpectType RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + extraArg: 'ERROR', // $ExpectError + }); + graphQLSelector<{foo: string}, string>({ // $ExpectError + key: 'key', + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + }); + graphQLSelector<{foo: string}, string>({ // $ExpectType RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: 'ERROR', // $ExpectError + mapResponse: data => data.str, + }); + + // graphQLSelectorFamily() + graphQLSelectorFamily<{foo: string}, string, string>({ // $ExpectType (parameter: string) => RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + }); + graphQLSelectorFamily<{foo: string}, string, string>({ // $ExpectType (parameter: string) => RecoilState + key: 'key', + environment: myEnvKey, + query: graphql`query...`, + variables: (str: string) => ({get}) => { + get; // $ExpectType GetRecoilValue + return null; + }, + mapResponse: data => data.str, + }); + graphQLSelectorFamily<{foo: string}, string, string>({ // $ExpectType (parameter: string) => RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + mutations: { + mutation: graphql`mutation...`, + variables: (str: string) => (param: string) => ({eggs: str}), + }, + }); + graphQLSelectorFamily<{foo: string}, string, string>({ // $ExpectType (parameter: string) => RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + extraArg: 'ERROR', // $ExpectError + }); + graphQLSelectorFamily<{foo: string}, string, string>({ // $ExpectError + key: 'key', + query: graphql`query...`, + variables: {foo: '123'}, + mapResponse: data => data.str, + }); + graphQLSelectorFamily<{foo: string}, string, string>({ // $ExpectType (parameter: string) => RecoilState + key: 'key', + environment: myEnv, + query: graphql`query...`, + variables: 'ERROR', // $ExpectError + mapResponse: data => data.str, + }); diff --git a/typescript/recoil-relay.d.ts b/typescript/recoil-relay.d.ts new file mode 100644 index 000000000..fae611659 --- /dev/null +++ b/typescript/recoil-relay.d.ts @@ -0,0 +1,129 @@ +// Minimum TypeScript Version: 3.9 + +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+recoil + */ + +/* + The current Relay TypeScript definitions aren't as careful as the Flow definitions. + The GraphQLTaggedNode type isn't parameterized, so there is nothing that checks + that the GraphQL query, subscription, or mutation actually has the expected type + for the variables or response. + */ + + import * as React from 'react'; + import { + RecoilState, AtomEffect, GetRecoilValue, SerializableParam, + } from 'recoil'; + import { + IEnvironment, + Variables, + GraphQLTaggedNode, + SelectorStoreUpdater, + UploadableMap, + } from 'relay-runtime'; + + export {}; + + // The Relay TypeScript definitions only declare the response as "unknown" + interface Response { [key: string]: any; } + + export class EnvironmentKey { + constructor(name: string); + toJSON(): string; + } + + export const RecoilRelayEnvironment: React.FC<{ + environmentKey: EnvironmentKey, + environment: IEnvironment, + children: React.ReactNode, + }>; + + export const RecoilRelayEnvironmentProvider: React.FC<{ + environmentKey: EnvironmentKey, + environment: IEnvironment, + children: React.ReactNode, + }>; + + export function graphQLQueryEffect(options: { + environment: IEnvironment | EnvironmentKey, + query: GraphQLTaggedNode, + variables: Variables | null, + mapResponse: (data: any) => T, + }): AtomEffect; + + export function graphQLSubscriptionEffect(options: { + environment: IEnvironment | EnvironmentKey, + subscription: GraphQLTaggedNode, + variables: Variables | null, + mapResponse: (data: any) => T, + }): AtomEffect; + + export function graphQLMutationEffect< + T, + TData extends Response + >(options: { + environment: IEnvironment | EnvironmentKey, + mutation: GraphQLTaggedNode, + variables: (newData: T) => Variables | null, + updater_UNSTABLE?: SelectorStoreUpdater, + optimisticUpdater_UNSTABLE?: SelectorStoreUpdater, + optimisticResponse_UNSTABLE?: (newData: T) => TData, + uploadables_UNSTABLE?: UploadableMap, + }): AtomEffect; + + export function graphQLSelector< + TVariables extends Variables, + T, + >(options: { + key: string, + environment: IEnvironment | EnvironmentKey, + query: GraphQLTaggedNode, + variables: + | TVariables + | ((callbacks: {get: GetRecoilValue}) => TVariables | null), + mapResponse: ( + response: any, + callbacks: {get: GetRecoilValue, variables: TVariables}, + ) => T, + default?: T, + mutations?: { + mutation: GraphQLTaggedNode, + variables: (newData: T) => Variables | null, + }, + }): RecoilState; + + export function graphQLSelectorFamily< + TVariables extends Variables, + P extends SerializableParam, + T, + TMutationVariables extends Variables = {}, + >(options: { + key: string, + environment: IEnvironment | EnvironmentKey, + query: GraphQLTaggedNode, + variables: + | TVariables + | ((parameter: P) => + | TVariables + | null + | ((callbacks: {get: GetRecoilValue}) => TVariables | null) + ), + mapResponse: ( + response: any, + callbacks: {get: GetRecoilValue, variables: TVariables}, + ) => T | ((paremter: P) => T), + default?: T | ((paremter: P) => T), + mutations?: { + mutation: GraphQLTaggedNode, + variables: (newData: T) => + | TMutationVariables + | null + | ((parameter: P) => TMutationVariables | null), + }, + }): (parameter: P) => RecoilState; diff --git a/typescript/recoil-sync-test.ts b/typescript/recoil-sync-test.ts index 7c7a259ea..54808d34a 100644 --- a/typescript/recoil-sync-test.ts +++ b/typescript/recoil-sync-test.ts @@ -1,4 +1,4 @@ -// Minimum TypeScript Version: 3.7 +// Minimum TypeScript Version: 3.9 /** * Copyright (c) Facebook, Inc. and its affiliates. diff --git a/typescript/recoil-sync.d.ts b/typescript/recoil-sync.d.ts index a7d9df2a5..7cb1d1a93 100644 --- a/typescript/recoil-sync.d.ts +++ b/typescript/recoil-sync.d.ts @@ -1,4 +1,4 @@ -// Minimum TypeScript Version: 3.7 +// Minimum TypeScript Version: 3.9 /** * Copyright (c) Facebook, Inc. and its affiliates. diff --git a/typescript/refine-test.ts b/typescript/refine-test.ts index 6fd5aaf91..f871281f0 100644 --- a/typescript/refine-test.ts +++ b/typescript/refine-test.ts @@ -1,4 +1,4 @@ -// Minimum TypeScript Version: 3.7 +// Minimum TypeScript Version: 3.9 /** * Copyright (c) Facebook, Inc. and its affiliates. diff --git a/typescript/refine.d.ts b/typescript/refine.d.ts index 2a70cab73..bf3a656db 100644 --- a/typescript/refine.d.ts +++ b/typescript/refine.d.ts @@ -1,4 +1,4 @@ -// Minimum TypeScript Version: 3.7 +// Minimum TypeScript Version: 3.9 /** * Copyright (c) Facebook, Inc. and its affiliates. diff --git a/typescript/tests.ts b/typescript/tests.ts index ad85539c5..598782843 100644 --- a/typescript/tests.ts +++ b/typescript/tests.ts @@ -1,3 +1,5 @@ +// Minimum TypeScript Version: 3.9 + /** * Copyright (c) Facebook, Inc. and its affiliates. * @@ -160,8 +162,8 @@ const selectorError3 = selector({ selectorError3; // RecoilRoot -RecoilRoot({children: React.createElement('div')}); -RecoilRoot({ +RecoilRoot({children: React.createElement('div')}); // $ExpectType ReactElement | null +RecoilRoot({ // $ExpectType ReactElement | null initializeState: ({ set, reset }) => { set(myAtom, 5); reset(myAtom); @@ -173,11 +175,11 @@ RecoilRoot({ }, children: React.createElement('div'), }); -RecoilRoot({ +RecoilRoot({ // $ExpectType ReactElement | null override: true, children: React.createElement('div'), }); -RecoilRoot({ +RecoilRoot({ // $ExpectType ReactElement | null override: false, children: React.createElement('div'), }); @@ -239,7 +241,7 @@ useRecoilState(waAtom); // $ExpectType [string, SetterOrUpdater] useRecoilState(waAtom); // $ExpectError useRecoilState(waAtom); // $ExpectError useRecoilValue(waAtom); // $ExpectError -useRecoilValue(waAtom); // $ExpectType string | number +const t8: string | number = useRecoilValue(waAtom); useRecoilValue(nsAtom); // $ExpectError useRecoilValue(myAtom); // $ExpectType number diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json index 90b21a172..e1908e6fc 100644 --- a/typescript/tsconfig.json +++ b/typescript/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "lib": ["es6"], + "lib": ["es6", "dom"], "noImplicitAny": true, "noImplicitThis": true, "strictFunctionTypes": true, @@ -14,10 +14,11 @@ }, "files": [ "index.d.ts", - "refine.d.ts", - "recoil-sync.d.ts", "tests.ts", + "refine.d.ts", "refine-test.ts", - "recoil-sync-test.ts" + "recoil-sync.d.ts", + "recoil-sync-test.ts", + "recoil-relay.d.ts" ] } diff --git a/yarn.lock b/yarn.lock index 9860a6239..ede7a5859 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1349,14 +1349,28 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react@>=16.13.1": - version "17.0.2" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" - integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA== +"@types/react-relay@>=13.0.2": + version "13.0.2" + resolved "https://registry.yarnpkg.com/@types/react-relay/-/react-relay-13.0.2.tgz#85ac65b13b7a67f8c75fc32dd175160c28d3cc9b" + integrity sha512-QyPV/BVKyv5/3bZKILIJYa2dM1r2HOMYNe6vuFbs/4G1uWj9RCbJiTcWFK2OxH3y70p1k9/A5gPS3lFDm0CvHQ== + dependencies: + "@types/react" "*" + "@types/relay-runtime" "*" + +"@types/react@*", "@types/react@>=18.0.9": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878" + integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw== dependencies: "@types/prop-types" "*" + "@types/scheduler" "*" csstype "^3.0.2" +"@types/relay-runtime@*", "@types/relay-runtime@>=13.0.3": + version "13.0.3" + resolved "https://registry.yarnpkg.com/@types/relay-runtime/-/relay-runtime-13.0.3.tgz#0139a02d24fd527c844af3063e943692fd181854" + integrity sha512-LZr9fiWspAtiFIMDcj/6LarYFqxd3jh3ASXULtWL5Tl5CHoDe48Il2nIAF66XKLLCG3QvDgHR8yLX6ZVWKjkkw== + "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -1370,6 +1384,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"