├── .gitignore ├── .npmignore ├── .prettierrc ├── src ├── index.ts ├── context.ts ├── subscription.ts ├── hooks.ts ├── mutation.ts ├── suspender.ts └── query.ts ├── tsconfig.json ├── package.json ├── README.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | .prettierrc 4 | tsconfig.json 5 | README.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "always", 3 | "tabWidth": 4, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context'; 2 | export * from './hooks'; 3 | export * from './mutation'; 4 | export * from './query'; 5 | export * from './subscription'; 6 | export * from './suspender'; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015"], 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode, useContext, createElement } from 'react'; 2 | import { Environment } from 'relay-runtime'; 3 | import { ReactRelayContext } from 'react-relay'; 4 | 5 | export function useEnvironment() { 6 | const context = useContext(ReactRelayContext); 7 | if (!context) { 8 | throw new Error('Missing Relay Context'); 9 | } 10 | return context.environment; 11 | } 12 | 13 | interface ProviderProps { 14 | relay: Environment; 15 | children: ReactNode; 16 | } 17 | 18 | export const Provider = ({ relay, children }: ProviderProps) => 19 | createElement(ReactRelayContext.Provider, { 20 | value: { environment: relay, variables: {} }, 21 | children, 22 | }); 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@levels3d/offblast", 3 | "version": "1.0.3", 4 | "description": "Hooks and Suspense wrapper for Relay", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "repository": "https://github.com/levels3d/offblast/", 8 | "author": "LevelS3D Dev ", 9 | "license": "MIT", 10 | "private": false, 11 | "keywords": [ 12 | "react", 13 | "relay", 14 | "graphql", 15 | "hooks", 16 | "suspense" 17 | ], 18 | "scripts": { 19 | "prepublish": "tsc" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^16.8.19", 23 | "@types/react-relay": "^1.3.14", 24 | "@types/relay-runtime": "^1.3.12", 25 | "react": "^16.8.6", 26 | "react-relay": "^5.0.0", 27 | "relay-runtime": "^5.0.0", 28 | "typescript": "^3.5.1" 29 | }, 30 | "peerDependencies": { 31 | "react": "^16.8.6", 32 | "react-relay": "^5.0.0", 33 | "relay-runtime": "^5.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/subscription.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { 3 | GraphQLTaggedNode, 4 | RelayMutationConfig as GenericMutationConfig, 5 | OperationBase, 6 | RecordSourceSelectorProxy, 7 | } from 'relay-runtime'; 8 | import { requestSubscription } from 'react-relay'; 9 | import { useEnvironment } from './context'; 10 | interface SubscriptionConfig { 11 | subscription: GraphQLTaggedNode; 12 | variables: T['variables']; 13 | configs?: GenericMutationConfig[]; 14 | updater?(store: RecordSourceSelectorProxy): void; 15 | onError?(error: Error): void; 16 | onNext?(response: T['response']): void; 17 | onCompleted?(): void; 18 | } 19 | export function useSubscription( 20 | config: SubscriptionConfig, 21 | inputs: any[], 22 | ) { 23 | const environment = useEnvironment(); 24 | useEffect(() => { 25 | const disposable = requestSubscription(environment, config); 26 | return () => disposable.dispose(); 27 | }, [environment, ...inputs]); 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Suspender } from './suspender'; 3 | 4 | export function useSuspense(value: Suspender): T { 5 | const state = value['state']; 6 | switch (state.state) { 7 | case 0: 8 | throw state.promise; 9 | case 1: 10 | return state.result; 11 | case 2: 12 | throw state.error; 13 | } 14 | } 15 | 16 | export function useSuspender(value: Suspender): T | null { 17 | const state = value['state']; 18 | switch (state.state) { 19 | case 0: 20 | return null; 21 | case 1: 22 | return state.result; 23 | case 2: 24 | throw state.error; 25 | } 26 | } 27 | 28 | export function useResult(value: Suspender): T | null { 29 | const inner = useSuspender(value); 30 | const [, setResult] = useState(inner); 31 | const state = value['state']; 32 | useEffect(() => { 33 | if (state.state === 0) { 34 | state.promise.then(setResult); 35 | } 36 | }, [state.state]); 37 | return inner; 38 | } 39 | -------------------------------------------------------------------------------- /src/mutation.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { 3 | GraphQLTaggedNode, 4 | RelayMutationConfig as GenericMutationConfig, 5 | OperationBase, 6 | UploadableMap, 7 | SelectorStoreUpdater, 8 | Disposable, 9 | } from 'relay-runtime'; 10 | import { commitMutation, _FragmentRefs } from 'react-relay'; 11 | 12 | import { useEnvironment } from './context'; 13 | 14 | type DeepPartial = T extends _FragmentRefs 15 | ? any 16 | : (T extends object ? { [K in keyof T]?: DeepPartial } : T); 17 | 18 | export interface MutationConfig { 19 | mutation: GraphQLTaggedNode; 20 | variables: T['variables']; 21 | configs?: GenericMutationConfig[]; 22 | uploadables?: UploadableMap; 23 | optimisticUpdater?: SelectorStoreUpdater; 24 | optimisticResponse?: DeepPartial; 25 | updater?: SelectorStoreUpdater; 26 | } 27 | 28 | export type Cancelable = Promise & { 29 | cancel(): void; 30 | }; 31 | 32 | function cancelable( 33 | thunk: ( 34 | resolve: (value: T) => void, 35 | reject: (error: any) => void, 36 | ) => Disposable, 37 | ): Cancelable { 38 | let disposable: Disposable | null = null; 39 | return Object.assign( 40 | new Promise((resolve, reject) => { 41 | disposable = thunk(resolve, reject); 42 | }), 43 | { 44 | cancel() { 45 | if (disposable) { 46 | disposable.dispose(); 47 | } 48 | }, 49 | }, 50 | ); 51 | } 52 | 53 | export function useMutation( 54 | configCallback: (...args: A) => MutationConfig, 55 | inputs: any[], 56 | ): (...args: A) => Cancelable { 57 | const environment = useEnvironment(); 58 | 59 | return useCallback( 60 | (...args: A) => 61 | cancelable((resolve, reject) => 62 | commitMutation(environment, { 63 | ...configCallback(...args), 64 | onCompleted(response, errors) { 65 | if (errors && errors.length > 0) { 66 | reject(errors); 67 | } else { 68 | resolve(response); 69 | } 70 | }, 71 | onError(error) { 72 | reject(error); 73 | }, 74 | }), 75 | ), 76 | [environment, ...inputs], 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /src/suspender.ts: -------------------------------------------------------------------------------- 1 | import { RelayObservable } from 'relay-runtime'; 2 | 3 | type SuspenderState = 4 | | { 5 | state: 0; 6 | promise: Promise; 7 | } 8 | | { 9 | state: 1; 10 | result: T; 11 | } 12 | | { 13 | state: 2; 14 | error: Error; 15 | }; 16 | 17 | type Resolvable = Promise & { 18 | resolve: (value: T) => void; 19 | reject: (err: Error) => void; 20 | }; 21 | 22 | function resolvable(): Resolvable { 23 | let resolve: (value: T) => void, reject: (err: any) => void; 24 | 25 | return Object.assign( 26 | new Promise((res, rej) => { 27 | resolve = res; 28 | reject = rej; 29 | }), 30 | { 31 | resolve(value: T) { 32 | resolve(value); 33 | }, 34 | reject(err: Error) { 35 | reject(err); 36 | }, 37 | }, 38 | ); 39 | } 40 | 41 | export class Suspender { 42 | private state: SuspenderState; 43 | 44 | private constructor(state: SuspenderState) { 45 | this.state = state; 46 | if (this.state.state === 0) { 47 | this.state.promise.then( 48 | result => { 49 | this.state = { 50 | state: 1, 51 | result, 52 | }; 53 | }, 54 | error => { 55 | this.state = { 56 | state: 2, 57 | error, 58 | }; 59 | }, 60 | ); 61 | } 62 | } 63 | 64 | static await(promise: Promise): Suspender { 65 | return new Suspender({ state: 0, promise }); 66 | } 67 | static resolve(result: T): Suspender { 68 | return new Suspender({ state: 1, result }); 69 | } 70 | static reject(error: Error): Suspender { 71 | return new Suspender({ state: 2, error }); 72 | } 73 | 74 | static observable(observable: RelayObservable): Suspender { 75 | const promise = resolvable(); 76 | const instance = new Suspender({ state: 0, promise }); 77 | observable.subscribe({ 78 | next(value) { 79 | promise.resolve(value); 80 | }, 81 | error(error) { 82 | promise.reject(error); 83 | }, 84 | }); 85 | return instance; 86 | } 87 | 88 | map(mapper: (input: T) => R): Suspender { 89 | switch (this.state.state) { 90 | case 0: 91 | return Suspender.await(this.state.promise.then(mapper)); 92 | case 1: 93 | return Suspender.resolve(mapper(this.state.result)); 94 | case 2: 95 | return Suspender.reject(this.state.error); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/query.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useState, useEffect, useRef } from 'react'; 2 | import { 3 | GraphQLTaggedNode, 4 | getRequest, 5 | // @ts-ignore 6 | createOperationDescriptor, 7 | OperationBase, 8 | OperationSelector, 9 | } from 'relay-runtime'; 10 | 11 | import { useEnvironment } from './context'; 12 | import { Suspender } from './suspender'; 13 | 14 | function hasChanged(prev: any[], next: any[]): boolean { 15 | return prev.some((value, index) => !Object.is(value, next[index])); 16 | } 17 | 18 | type Skip = 'lookup' | 'execute' | boolean; 19 | 20 | type QueryArgs = 21 | | [GraphQLTaggedNode, any[]] 22 | | [GraphQLTaggedNode, T['variables'], any[]] 23 | | [GraphQLTaggedNode, T['variables'], Skip, any[]]; 24 | type ArgsTuple = [ 25 | GraphQLTaggedNode, 26 | T['variables'] | undefined, 27 | boolean, 28 | boolean, 29 | any[] 30 | ]; 31 | 32 | function extractArgs( 33 | args: QueryArgs, 34 | ): ArgsTuple { 35 | switch (args.length) { 36 | case 2: 37 | const [query2, inputs2] = args; 38 | return [query2, undefined, false, false, inputs2]; 39 | case 3: 40 | const [query3, variables3, inputs3] = args; 41 | return [query3, variables3, false, false, inputs3]; 42 | case 4: 43 | const [query4, variables4, skip4, inputs4] = args; 44 | return [ 45 | query4, 46 | variables4, 47 | skip4 === true || skip4 === 'lookup', 48 | skip4 === true || skip4 === 'execute', 49 | inputs4, 50 | ]; 51 | } 52 | } 53 | 54 | export function useQuery( 55 | taggedNode: GraphQLTaggedNode, 56 | inputs: any[], 57 | ): Suspender; 58 | export function useQuery( 59 | taggedNode: GraphQLTaggedNode, 60 | variables: T['variables'], 61 | inputs: any[], 62 | ): Suspender; 63 | export function useQuery( 64 | taggedNode: GraphQLTaggedNode, 65 | variables: T['variables'], 66 | skip: 'lookup', 67 | inputs: any[], 68 | ): Suspender; 69 | export function useQuery( 70 | taggedNode: GraphQLTaggedNode, 71 | variables: T['variables'], 72 | skip: 'execute' | boolean, 73 | inputs: any[], 74 | ): Suspender; 75 | 76 | export function useQuery( 77 | ...args: QueryArgs 78 | ): Suspender { 79 | const [ 80 | taggedNode, 81 | variables, 82 | skipLookup, 83 | skipExecute, 84 | inputs, 85 | ] = extractArgs(args); 86 | 87 | const environment = useEnvironment(); 88 | 89 | const operation: OperationSelector = useMemo(() => { 90 | if (skipLookup && skipExecute) { 91 | return null; 92 | } 93 | const query = getRequest(taggedNode); 94 | if (query.params.operationKind !== 'query') { 95 | throw new Error('useQuery: Expected query operation'); 96 | } 97 | return createOperationDescriptor(query, variables); 98 | }, inputs); 99 | 100 | const [data, setData] = useState(() => { 101 | if (!skipLookup && environment.check(operation.root)) { 102 | const snapshot = environment.lookup(operation.fragment); 103 | return snapshot.data; 104 | } 105 | return null; 106 | }); 107 | 108 | const prevInputs = useRef(inputs); 109 | if (hasChanged(prevInputs.current, inputs)) { 110 | prevInputs.current = inputs; 111 | setData(null); 112 | } 113 | 114 | useEffect(() => { 115 | if (skipLookup && skipExecute) { 116 | return; 117 | } 118 | 119 | if (environment.check(operation.root)) { 120 | const snapshot = environment.lookup(operation.fragment); 121 | const retain = environment.retain(operation.root); 122 | 123 | const subscribe = environment.subscribe(snapshot, () => { 124 | if (environment.check(operation.root)) { 125 | const { data } = environment.lookup(operation.fragment); 126 | setData(data || null); 127 | } else { 128 | setData(null); 129 | } 130 | }); 131 | 132 | return () => { 133 | retain.dispose(); 134 | subscribe.dispose(); 135 | }; 136 | } 137 | }, [skipLookup, skipExecute, environment, operation, data, setData]); 138 | 139 | const request = useRef | null>(null); 140 | const cancelled = useRef(false); 141 | useEffect( 142 | () => () => { 143 | cancelled.current = true; 144 | }, 145 | [], 146 | ); 147 | 148 | if (data !== null) { 149 | return Suspender.resolve(data); 150 | } 151 | 152 | if (skipExecute) { 153 | return Suspender.resolve(null); 154 | } 155 | 156 | if (request.current === null) { 157 | request.current = Suspender.observable( 158 | environment.execute({ operation }).map(() => { 159 | const { data = null } = environment.lookup(operation.fragment); 160 | if (!cancelled.current) { 161 | setData(data); 162 | } 163 | return data; 164 | }), 165 | ); 166 | } 167 | 168 | return request.current; 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Offblast 2 | 3 | Offblast is a helper library for [Relay](https://github.com/facebook/relay/) 4 | that provides [React](https://github.com/facebook/react/) hooks to send GraphQL 5 | queries, mutations and subscriptions from functional components. 6 | 7 | ## Context 8 | 9 | Offblast uses the `ReactRelayContext` from `react-relay` to access the current 10 | Relay environment. Depending on how you use this library along with 11 | `react-relay`, you may need to add a `` component higher in the 12 | React tree to use the hooks. 13 | 14 | ## Suspender 15 | 16 | `Suspender` is a utility class provided by this library that acts as a 17 | wrapper for a `Promise`, with the ability to query its result synchronously 18 | once it has resolved (or its error if it has rejected). 19 | 20 | This is useful as a common pattern with GraphQL and Relay is to merge all the 21 | data needed by a page in a single query, but you don't necessarily want to show 22 | a single loading indicator for the whole page but only in the places where data 23 | is missing. The `Suspender` class exposes a `map` method with the signature 24 | `map(func: (value: T) => U): Suspender` to help you narrow a query result 25 | to a specific bit of data while passing the Suspender object down the props of 26 | the components to the point where the data is actually needed, which is where a 27 | loading indicator should be displayed. 28 | 29 | ## Hooks 30 | 31 | ### `useQuery` 32 | 33 | The `useQuery` hook has the following signature: 34 | 35 | ```typescript 36 | function useQuery( 37 | query: GraphQLTaggedNode, 38 | variables?: T["variables"], 39 | skip?: "execute" | "lookup" | boolean, 40 | inputs: any[] 41 | ): Suspender; 42 | ``` 43 | 44 | This hook executes the GraphQL query specified in `query` with the variables 45 | optionally specified in `variables`, and returns a `Suspender` that will 46 | eventually resolve to the result of said query. 47 | 48 | `useQuery` will also subscribe to updates of the Relay cache, so your components 49 | will automatically re-render when the data previously returned by the hook has 50 | changed. 51 | 52 | Please note that the hook does **NOT** check if your variables or your query has 53 | changed since the last render: instead, is uses the same pattern as native hooks 54 | such as `useEffect` or `useMemo` and exposes an `input: any[]` parameter that is 55 | used to detect whether the query should be executed again. 56 | 57 | Finally, the hooks has a `skip` for advanced use cases where you want to 58 | partially or completely skip the query execution. Since the Rule of Hooks 59 | forbids hooks in `if` branches, setting this argument to `true` will skip the 60 | execution of the query and return a `Suspender` that resolves immediately. 61 | Alternatively, you can set the `skip` parameter to `'lookup'` to skip the Relay 62 | cache lookup and force the query to be sent to the server, or `'execute'` to 63 | skip the execution of the query and only query the Relay cache, possibly 64 | returning incomplete data. 65 | 66 | ### `useMutation` 67 | 68 | The `useMutation` hook has the following signature: 69 | 70 | ```typescript 71 | function useMutation( 72 | config: (...args: A) => MutationConfig, 73 | inputs: any[] 74 | ): (...args: A) => Cancelable; 75 | ``` 76 | 77 | It is essentially a wrapper for `useCallback`, and returns a function that will 78 | in turn call `commitMutation` with the configuration you provided. Just like 79 | `useMemo` it takes 2 parameters: `config` is a function that returns a 80 | `MutationConfig` object, and `inputs` is an arbitrary array that drives the 81 | memoization of the returned function. The `Cancelable` returned by the 82 | callback function is a `Promise` with an additional `cancel(): void` method 83 | that will dispose of the internal operation. 84 | 85 | ### `useSubscription` 86 | 87 | The `useSubscription` hook has the following signature: 88 | 89 | ```typescript 90 | function useSubscription( 91 | config: SubscriptionConfig, 92 | inputs: any[] 93 | ): void; 94 | ``` 95 | 96 | The `config` object describes the subscription to be created, and the `inputs` 97 | array is used to determine whether to configuration has changed and the 98 | subscription should be recreated. Note that this hook does not return anything: 99 | it is expected that your configuration will use an `updater` function to mutate 100 | the Relay store in response to subscription updates which will automatically 101 | trigger a re-render of any query that uses the modified values. 102 | 103 | ### `useEnvironment` 104 | 105 | The `useEnvironment` hook has the following signature: 106 | 107 | ```typescript 108 | function useEnvironment(): Environment; 109 | ``` 110 | 111 | It simply returns the current Relay environment, or throw an error if there is 112 | none (usually this is because you need to have a `` component higher 113 | in the React tree). 114 | 115 | ### `useResult` 116 | 117 | The `useResult` hook has the following signature: 118 | 119 | ```typescript 120 | function useResult(value: Suspender): T | null; 121 | ``` 122 | 123 | This hook unwraps a `Suspender` object. If it has already resolved, the result 124 | is returned and can be used immediately. If it has rejected, the error is thrown 125 | and can be caught using the `componentDidCatch` lifecycle method. Finally, if it 126 | hasn't resolved yet, the hooks will return `null` and subscribe to it in order 127 | to trigger a re-render when it will eventually resolve. 128 | 129 | ### `useSuspender` 130 | 131 | The `useSuspender` hook has the following signature: 132 | 133 | ```typescript 134 | function useSuspender(value: Suspender): T | null; 135 | ``` 136 | 137 | This hook unwraps a `Suspender` object. If it has already resolved, the result 138 | is returned and can be used immediately. If it has rejected, the error is thrown 139 | and can be caught using the `componentDidCatch` lifecycle method. Finally, if it 140 | hasn't resolved yet, the hooks will return `null`. Note that unlike `useResult`, 141 | this hooks does **NOT** subscribe to the `Suspender` and will **NOT** trigger a 142 | re-render if it resolves after having previously returned `null`. 143 | 144 | ### `useSuspense` 145 | 146 | The `useSuspense` hook has the following signature: 147 | 148 | ```typescript 149 | function useSuspense(value: Suspender): T; 150 | ``` 151 | 152 | This hook unwraps a `Suspender` object. If it has already resolved, the result 153 | is returned and can be used immediately. If it has rejected, the error is thrown 154 | and can be caught using the `componentDidCatch` lifecycle method. Finally, if it 155 | hasn't resolved yet, the `Suspender`'s internal `Promise` will be thrown which 156 | will cause the current React tree (up to the nearest `` 157 | component) to become suspended. 158 | 159 | Please note that React Suspense is **NOT STABLE** yet, please use this hook at 160 | your own risk. 161 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.0.0": 6 | version "7.4.5" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" 8 | integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== 9 | dependencies: 10 | regenerator-runtime "^0.13.2" 11 | 12 | "@types/prop-types@*": 13 | version "15.7.1" 14 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" 15 | integrity sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg== 16 | 17 | "@types/react-relay@^1.3.14": 18 | version "1.3.14" 19 | resolved "https://registry.yarnpkg.com/@types/react-relay/-/react-relay-1.3.14.tgz#72b590be03b193feca4c732370d80647fb64bda7" 20 | integrity sha512-Zh5IpiG15D3eZQuhxnqpBrxcszmGv1JfDB4BdBkd4WnfZ0iHUqHTLX506BT/Nk8x9fezfqxOv9c4W8yITZizMQ== 21 | dependencies: 22 | "@types/react" "*" 23 | "@types/relay-runtime" "*" 24 | 25 | "@types/react@*", "@types/react@^16.8.19": 26 | version "16.8.19" 27 | resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.19.tgz#629154ef05e2e1985cdde94477deefd823ad9be3" 28 | integrity sha512-QzEzjrd1zFzY9cDlbIiFvdr+YUmefuuRYrPxmkwG0UQv5XF35gFIi7a95m1bNVcFU0VimxSZ5QVGSiBmlggQXQ== 29 | dependencies: 30 | "@types/prop-types" "*" 31 | csstype "^2.2.0" 32 | 33 | "@types/relay-runtime@*", "@types/relay-runtime@^1.3.12": 34 | version "1.3.12" 35 | resolved "https://registry.yarnpkg.com/@types/relay-runtime/-/relay-runtime-1.3.12.tgz#602b98eb333b47d98213816d7e40273edaac3e90" 36 | integrity sha512-MXM7ElUpLzPfaIlG6263qDd7+zdNpqy43K90yyQzB0ax1hKMJ+R3WfaVLFxn/X49YR8fa6kwA3ikjh2A/JNFJg== 37 | 38 | asap@~2.0.3: 39 | version "2.0.6" 40 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 41 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 42 | 43 | core-js@^2.4.1: 44 | version "2.6.9" 45 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" 46 | integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== 47 | 48 | csstype@^2.2.0: 49 | version "2.6.5" 50 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.5.tgz#1cd1dff742ebf4d7c991470ae71e12bb6751e034" 51 | integrity sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA== 52 | 53 | encoding@^0.1.11: 54 | version "0.1.12" 55 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 56 | integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= 57 | dependencies: 58 | iconv-lite "~0.4.13" 59 | 60 | fbjs-css-vars@^1.0.0: 61 | version "1.0.2" 62 | resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" 63 | integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== 64 | 65 | fbjs@^1.0.0: 66 | version "1.0.0" 67 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" 68 | integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== 69 | dependencies: 70 | core-js "^2.4.1" 71 | fbjs-css-vars "^1.0.0" 72 | isomorphic-fetch "^2.1.1" 73 | loose-envify "^1.0.0" 74 | object-assign "^4.1.0" 75 | promise "^7.1.1" 76 | setimmediate "^1.0.5" 77 | ua-parser-js "^0.7.18" 78 | 79 | iconv-lite@~0.4.13: 80 | version "0.4.24" 81 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 82 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 83 | dependencies: 84 | safer-buffer ">= 2.1.2 < 3" 85 | 86 | is-stream@^1.0.1: 87 | version "1.1.0" 88 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 89 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= 90 | 91 | isomorphic-fetch@^2.1.1: 92 | version "2.2.1" 93 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 94 | integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= 95 | dependencies: 96 | node-fetch "^1.0.1" 97 | whatwg-fetch ">=0.10.0" 98 | 99 | "js-tokens@^3.0.0 || ^4.0.0": 100 | version "4.0.0" 101 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 102 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 103 | 104 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: 105 | version "1.4.0" 106 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 107 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 108 | dependencies: 109 | js-tokens "^3.0.0 || ^4.0.0" 110 | 111 | node-fetch@^1.0.1: 112 | version "1.7.3" 113 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" 114 | integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== 115 | dependencies: 116 | encoding "^0.1.11" 117 | is-stream "^1.0.1" 118 | 119 | nullthrows@^1.1.0: 120 | version "1.1.1" 121 | resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" 122 | integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== 123 | 124 | object-assign@^4.1.0, object-assign@^4.1.1: 125 | version "4.1.1" 126 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 127 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 128 | 129 | promise@^7.1.1: 130 | version "7.3.1" 131 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 132 | integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== 133 | dependencies: 134 | asap "~2.0.3" 135 | 136 | prop-types@^15.6.2: 137 | version "15.7.2" 138 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" 139 | integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== 140 | dependencies: 141 | loose-envify "^1.4.0" 142 | object-assign "^4.1.1" 143 | react-is "^16.8.1" 144 | 145 | react-is@^16.8.1: 146 | version "16.8.6" 147 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" 148 | integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== 149 | 150 | react-relay@^5.0.0: 151 | version "5.0.0" 152 | resolved "https://registry.yarnpkg.com/react-relay/-/react-relay-5.0.0.tgz#66af68e8e5fad05879a3f21f895a0296ef2741a8" 153 | integrity sha512-gpUvedaCaPVPT0nMrTbev2TzrU0atgq2j/zAnGHiR9WgqRXwtHsK6FWFN65HRbopO2DzuJx9VZ2I3VO6uL5EMA== 154 | dependencies: 155 | "@babel/runtime" "^7.0.0" 156 | fbjs "^1.0.0" 157 | nullthrows "^1.1.0" 158 | relay-runtime "5.0.0" 159 | 160 | react@^16.8.6: 161 | version "16.8.6" 162 | resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" 163 | integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== 164 | dependencies: 165 | loose-envify "^1.1.0" 166 | object-assign "^4.1.1" 167 | prop-types "^15.6.2" 168 | scheduler "^0.13.6" 169 | 170 | regenerator-runtime@^0.13.2: 171 | version "0.13.2" 172 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" 173 | integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== 174 | 175 | relay-runtime@5.0.0, relay-runtime@^5.0.0: 176 | version "5.0.0" 177 | resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-5.0.0.tgz#7c688ee621d6106a2cd9f3a3706eb6d717c7f660" 178 | integrity sha512-lrC2CwfpWWHBAN608eENAt5Bc5zqXXE2O9HSo8tc6Gy5TxfK+fU+x9jdwXQ2mXxVPgANYtYeKzU5UTfcX0aDEw== 179 | dependencies: 180 | "@babel/runtime" "^7.0.0" 181 | fbjs "^1.0.0" 182 | 183 | "safer-buffer@>= 2.1.2 < 3": 184 | version "2.1.2" 185 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 186 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 187 | 188 | scheduler@^0.13.6: 189 | version "0.13.6" 190 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" 191 | integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== 192 | dependencies: 193 | loose-envify "^1.1.0" 194 | object-assign "^4.1.1" 195 | 196 | setimmediate@^1.0.5: 197 | version "1.0.5" 198 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 199 | integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= 200 | 201 | typescript@^3.5.1: 202 | version "3.5.1" 203 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" 204 | integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== 205 | 206 | ua-parser-js@^0.7.18: 207 | version "0.7.20" 208 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" 209 | integrity sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw== 210 | 211 | whatwg-fetch@>=0.10.0: 212 | version "3.0.0" 213 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" 214 | integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== 215 | --------------------------------------------------------------------------------