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"