diff --git a/change/@nova-examples-f141d6aa-2a6f-4424-be8b-c0ad36696cc4.json b/change/@nova-examples-f141d6aa-2a6f-4424-be8b-c0ad36696cc4.json new file mode 100644 index 0000000..169be20 --- /dev/null +++ b/change/@nova-examples-f141d6aa-2a6f-4424-be8b-c0ad36696cc4.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "[examples] Add example Relay provider and export relay Feedback example component", + "packageName": "@nova/examples", + "email": "eloy.de.enige@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/examples/README.md b/packages/examples/README.md index 3ed7b4c..a48c23b 100644 --- a/packages/examples/README.md +++ b/packages/examples/README.md @@ -1,3 +1,45 @@ # Nova examples This package contains examples of nova components as well as related good testing approaches for them. + +## Nova host integration canary + +When configuring a host application, this package can be used as a light-weight canary package to test the host's Nova integration. + +### GraphQL + +#### Relay + +- [Configure your project to have Relay.](https://relay.dev/docs/getting-started/installation-and-setup/) +- [Create a Relay environment.](https://relay.dev/docs/api-reference/relay-environment-provider/) +- Instantiate the Nova Relay example provider: + +```tsx +import { + NovaExampleRelayGraphQLProvider, + NovaExampleRelayComponent, +} from "@nova/examples"; +import { createRelayEnvironment } from "./myHostRelayEnvironment"; + +const environment = createRelayEnvironment(); + +const MyHostNovaIntegrationTest = () => { + return ( + + + + ); +}; +``` + +#### Apollo + +TODO + +### Eventing + +TODO + +### Commanding + +TODO diff --git a/packages/examples/package.json b/packages/examples/package.json index 962d2f7..e80d28a 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -25,6 +25,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-relay": "^18.0.0", + "relay-hooks": "^9.2.0", "relay-runtime": "^18.0.0" }, "devDependencies": { @@ -57,5 +58,18 @@ "storybook": "^8.4.5", "typescript": "^5.6.0" }, - "sideEffects": false + "sideEffects": false, + "access": "public", + "publishConfig": { + "main": "./lib/index", + "types": "./lib/index.d.ts", + "module": "./lib/index.mjs", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.mjs", + "require": "./lib/index.js" + } + } + } } diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts new file mode 100644 index 0000000..8422f5f --- /dev/null +++ b/packages/examples/src/index.ts @@ -0,0 +1,2 @@ +export { NovaExampleRelayGraphQLProvider } from "./relay/NovaExampleRelayGraphQLProvider"; +export { FeedbackContainer as NovaExampleRelayComponent } from "./relay/Feedback/FeedbackContainer"; diff --git a/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/index.tsx b/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/index.tsx new file mode 100644 index 0000000..0384d56 --- /dev/null +++ b/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/index.tsx @@ -0,0 +1,35 @@ +import type { NovaGraphQL } from "@nova/types"; +import type RelayModernEnvironment from "relay-runtime/lib/store/RelayModernEnvironment"; + +import * as React from "react"; +import { + useFragment, + useRefetchableFragment, + usePaginationFragment, + useSubscription, + RelayEnvironmentProvider, +} from "react-relay/hooks"; +import { NovaGraphQLProvider } from "@nova/react"; +import { useRelayLazyLoadQueryWithApolloAPI } from "./useRelayLazyLoadQueryWithApolloAPI"; +import { useRelayMutationWithApolloAPI } from "./useRelayMutationWithApolloAPI"; + +const GraphQLInterface: NovaGraphQL = { + useFragment, + useRefetchableFragment, + usePaginationFragment, + useLazyLoadQuery: useRelayLazyLoadQueryWithApolloAPI, + useMutation: useRelayMutationWithApolloAPI, + useSubscription, +}; + +export const NovaExampleRelayGraphQLProvider: React.FC< + React.PropsWithChildren<{ relayEnvironment: RelayModernEnvironment }> +> = ({ children, relayEnvironment }) => { + return ( + + + {children} + + + ); +}; diff --git a/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/useRelayLazyLoadQueryWithApolloAPI.ts b/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/useRelayLazyLoadQueryWithApolloAPI.ts new file mode 100644 index 0000000..d68b53f --- /dev/null +++ b/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/useRelayLazyLoadQueryWithApolloAPI.ts @@ -0,0 +1,30 @@ +/** + * NOTE: Taken from TMP packages/frameworks/frameworks-relay/src/react/hooks + * + * If needing to make changes, please make them in the upstream frameworks package. + */ + +import { useQuery } from "relay-hooks"; +import type { + FetchPolicy, + GraphQLTaggedNode, + OperationType, +} from "relay-runtime"; + +/** + * Hook that wraps useQuery hook from "relay-hooks" library and returns a Nova API compatible response. + * We can't use original Relay useLazyLoadQuery, since its implementation is heavily relies on Suspense. + */ +export function useRelayLazyLoadQueryWithApolloAPI< + TQuery extends OperationType, +>( + query: GraphQLTaggedNode, + variables: TQuery["variables"], + options?: { fetchPolicy?: FetchPolicy }, +): { error?: Error; data?: TQuery["response"] } { + const result = useQuery(query, variables, options); + return { + ...result, + error: result.error ?? undefined, + }; +} diff --git a/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/useRelayMutationWithApolloAPI.ts b/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/useRelayMutationWithApolloAPI.ts new file mode 100644 index 0000000..df81d84 --- /dev/null +++ b/packages/examples/src/relay/NovaExampleRelayGraphQLProvider/useRelayMutationWithApolloAPI.ts @@ -0,0 +1,52 @@ +/** + * NOTE: Taken from TMP packages/frameworks/frameworks-relay/src/react/hooks + * + * If needing to make changes, please make them in the upstream frameworks package. + */ + +import { useMutation as useRelayMutation } from "react-relay/hooks"; +import type { GraphQLTaggedNode, MutationParameters } from "relay-runtime"; + +type MutationCommitter = (options: { + variables: { [name: string]: unknown }; + context?: { [name: string]: unknown }; + optimisticResponse?: unknown | null; + onCompleted?: (response: unknown) => void; +}) => Promise<{ errors?: Error[]; data?: TMutation["response"] }>; + +/** + * Hook that wraps original Relay useMutation hook and returns mutation committer, + * that returns a promise. This is required for alignment with Apollo client + * useMutation hook API for smoother migration of production code to Relay. + * @param mutation Mutation document. + * @returns Array containing mutation committer function and a flag + * indicating whether mutation is "in flight". + */ +export function useRelayMutationWithApolloAPI< + TMutation extends MutationParameters, +>(mutation: GraphQLTaggedNode): [MutationCommitter, boolean] { + const [commitMutation, isMutationInFlight] = useRelayMutation(mutation); + return [ + (options) => { + return new Promise((resolve, reject) => { + commitMutation({ + variables: options.variables, + onCompleted: (response, errors) => { + resolve( + errors + ? { + data: response, + errors: errors.map((e) => new Error(e.message)), + } + : { data: response }, + ); + }, + onError: (error) => { + reject(error); + }, + }); + }); + }, + isMutationInFlight, + ]; +} diff --git a/yarn.lock b/yarn.lock index 2b10aed..e0225aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1694,6 +1694,13 @@ resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca" integrity sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA== +"@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== + dependencies: + dequal "^2.0.3" + "@rushstack/node-core-library@3.45.5": version "3.45.5" resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.45.5.tgz#00f92143cc21c3ad94fcd81ba168a40ac8cb77f2" @@ -7014,6 +7021,14 @@ relay-compiler@^18.0.0: resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-18.1.0.tgz#ae6f82a3c967d6e2bb35c4e7971acae0b6a36e47" integrity sha512-DGWIORIij0TIys/Xd5W/iToqTv7UcN6fqCPDqwYhLnfU7ukdK8rpk7f6RCYvhyZG71e7a1b3TLGOQY8A3XE3yg== +relay-hooks@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/relay-hooks/-/relay-hooks-9.2.0.tgz#7785b7ac8ebafb9a8d0b045a2e622668168fe1c0" + integrity sha512-6y4RjF0sGt85kxRM87eQlHFl4utBI2fXfFM7TNJ+CiYGaH7e65hTYsh65j/8B5IzGp93hevG0yVaeY/Zeapahg== + dependencies: + "@restart/hooks" "^0.4.9" + fbjs "^3.0.2" + relay-runtime@12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-12.0.0.tgz#1e039282bdb5e0c1b9a7dc7f6b9a09d4f4ff8237"