diff --git a/.changeset/common-parks-fix.md b/.changeset/common-parks-fix.md new file mode 100644 index 00000000..98c2f8f8 --- /dev/null +++ b/.changeset/common-parks-fix.md @@ -0,0 +1,11 @@ +--- +"@graphprotocol/hypergraph": patch +"@graphprotocol/hypergraph-react": patch +--- + +Add configurable API origin support + +- Add `Config.setApiOrigin()` and `Config.getApiOrigin()` functions to allow setting a custom API origin globally +- Add `apiOrigin` prop to `HypergraphAppProvider` for React apps +- Replace all hardcoded `Graph.TESTNET_API_ORIGIN` references with configurable `Config.getApiOrigin()` +- Default behavior remains unchanged (uses testnet) if not configured diff --git a/packages/hypergraph-react/src/HypergraphAppContext.tsx b/packages/hypergraph-react/src/HypergraphAppContext.tsx index c93814da..72d35692 100644 --- a/packages/hypergraph-react/src/HypergraphAppContext.tsx +++ b/packages/hypergraph-react/src/HypergraphAppContext.tsx @@ -7,6 +7,7 @@ import { Repo } from '@automerge/automerge-repo/slim'; import { RepoContext } from '@automerge/automerge-repo-react-hooks'; import { Graph } from '@graphprotocol/grc-20'; import { + Config, Connect, type ConnectCallbackResult, Identity, @@ -232,6 +233,7 @@ export type HypergraphAppProviderProps = Readonly<{ children: ReactNode; appId: string; logInvalidResults?: boolean; + apiOrigin?: string; }>; const mockStorage = { @@ -249,6 +251,7 @@ export function HypergraphAppProvider({ appId, children, logInvalidResults = true, + apiOrigin, }: HypergraphAppProviderProps) { const [websocketConnection, setWebsocketConnection] = useState(); const [isConnecting, setIsConnecting] = useState(true); @@ -259,6 +262,12 @@ export function HypergraphAppProvider({ const identity = useSelectorStore(store, (state) => state.context.identity); const privyIdentity = useSelectorStore(store, (state) => state.context.privyIdentity); + useEffect(() => { + if (apiOrigin) { + Config.setApiOrigin(apiOrigin); + } + }, [apiOrigin]); + const logout = useCallback(() => { websocketConnection?.close(); setWebsocketConnection(undefined); diff --git a/packages/hypergraph-react/src/hooks/use-spaces.ts b/packages/hypergraph-react/src/hooks/use-spaces.ts index ff3a0742..15b300dc 100644 --- a/packages/hypergraph-react/src/hooks/use-spaces.ts +++ b/packages/hypergraph-react/src/hooks/use-spaces.ts @@ -1,5 +1,4 @@ -import { Graph } from '@graphprotocol/grc-20'; -import { store } from '@graphprotocol/hypergraph'; +import { Config, store } from '@graphprotocol/hypergraph'; import { useQuery } from '@tanstack/react-query'; import { useSelector } from '@xstate/store/react'; import { gql, request } from 'graphql-request'; @@ -10,7 +9,6 @@ const publicSpacesQueryDocument = gql` query Spaces($accountAddress: String!) { spaces(filter: {members: {some: {address: {is: $accountAddress}}}}) { id - spaceAddress page { name } @@ -21,7 +19,6 @@ query Spaces($accountAddress: String!) { type PublicSpacesQueryResult = { spaces: { id: string; - spaceAddress: string; page: { name: string; } | null; @@ -38,7 +35,7 @@ export const useSpaces = (params: { mode: 'public' | 'private' }) => { queryKey: ['hypergraph-public-spaces', params.mode], queryFn: async () => { const result = await request( - `${Graph.TESTNET_API_ORIGIN}/v2/graphql`, + `${Config.getApiOrigin()}/v2/graphql`, publicSpacesQueryDocument, { accountAddress, @@ -48,7 +45,6 @@ export const useSpaces = (params: { mode: 'public' | 'private' }) => { ? result.spaces.map((space) => ({ id: space.id, name: space.page?.name, - spaceAddress: space.spaceAddress, })) : []; }, diff --git a/packages/hypergraph-react/src/internal/generate-delete-ops.tsx b/packages/hypergraph-react/src/internal/generate-delete-ops.tsx index 4a16f486..65fcc7eb 100644 --- a/packages/hypergraph-react/src/internal/generate-delete-ops.tsx +++ b/packages/hypergraph-react/src/internal/generate-delete-ops.tsx @@ -1,4 +1,5 @@ -import { Graph, type Op } from '@graphprotocol/grc-20'; +import type { Op } from '@graphprotocol/grc-20'; +import { Config } from '@graphprotocol/hypergraph'; import { gql, request } from 'graphql-request'; const deleteEntityQueryDocument = gql` @@ -44,13 +45,9 @@ type DeleteEntityResult = { }; export const generateDeleteOps = async ({ id }: { id: string; space: string }) => { - const result = await request( - `${Graph.TESTNET_API_ORIGIN}/v2/graphql`, - deleteEntityQueryDocument, - { - entityId: id, - }, - ); + const result = await request(`${Config.getApiOrigin()}/v2/graphql`, deleteEntityQueryDocument, { + entityId: id, + }); if (result.entity === null) { throw new Error('Entity not found'); } diff --git a/packages/hypergraph-react/src/internal/use-delete-entity-public.tsx b/packages/hypergraph-react/src/internal/use-delete-entity-public.tsx index 70f9690b..0f13b059 100644 --- a/packages/hypergraph-react/src/internal/use-delete-entity-public.tsx +++ b/packages/hypergraph-react/src/internal/use-delete-entity-public.tsx @@ -1,6 +1,6 @@ import { Graph, type Op } from '@graphprotocol/grc-20'; import type { Connect } from '@graphprotocol/hypergraph'; -import { Constants } from '@graphprotocol/hypergraph'; +import { Config, Constants } from '@graphprotocol/hypergraph'; import { useQueryClient } from '@tanstack/react-query'; import * as Option from 'effect/Option'; import type * as Schema from 'effect/Schema'; @@ -45,7 +45,7 @@ export const useDeleteEntityPublic = ( return async ({ id, walletClient }: { id: string; walletClient: Connect.SmartSessionClient }) => { try { const result = await request( - `${Graph.TESTNET_API_ORIGIN}/v2/graphql`, + `${Config.getApiOrigin()}/v2/graphql`, deleteEntityQueryDocument, { spaceId: space, diff --git a/packages/hypergraph-react/src/internal/use-public-space.tsx b/packages/hypergraph-react/src/internal/use-public-space.tsx index 2f26ee88..18993089 100644 --- a/packages/hypergraph-react/src/internal/use-public-space.tsx +++ b/packages/hypergraph-react/src/internal/use-public-space.tsx @@ -1,4 +1,4 @@ -import { Graph } from '@graphprotocol/grc-20'; +import { Config } from '@graphprotocol/hypergraph'; import { useQuery } from '@tanstack/react-query'; import { gql, request } from 'graphql-request'; @@ -24,7 +24,7 @@ export const usePublicSpace = ({ spaceId, enabled }: { spaceId: string; enabled: const result = useQuery({ queryKey: ['hypergraph-public-space', spaceId], queryFn: async () => { - const result = await request(`${Graph.TESTNET_API_ORIGIN}/v2/graphql`, spaceQueryDocument, { + const result = await request(`${Config.getApiOrigin()}/v2/graphql`, spaceQueryDocument, { spaceId, }); return result?.space?.page diff --git a/packages/hypergraph-react/src/prepare-publish.ts b/packages/hypergraph-react/src/prepare-publish.ts index ce9a1327..c7ebd50e 100644 --- a/packages/hypergraph-react/src/prepare-publish.ts +++ b/packages/hypergraph-react/src/prepare-publish.ts @@ -1,5 +1,5 @@ import { Graph, type Id, type Op, type PropertiesParam, type RelationsParam } from '@graphprotocol/grc-20'; -import { Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; +import { Config, Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; import * as Option from 'effect/Option'; import type * as Schema from 'effect/Schema'; import * as SchemaAST from 'effect/SchemaAST'; @@ -49,7 +49,7 @@ export const preparePublish = async ({ publicSpace, }: PreparePublishParams) => { const data = await request( - `${Graph.TESTNET_API_ORIGIN}/v2/graphql`, + `${Config.getApiOrigin()}/v2/graphql`, entityToPublishQueryDocument, { entityId: entity.id, diff --git a/packages/hypergraph-react/src/publish-ops.ts b/packages/hypergraph-react/src/publish-ops.ts index d84d88c0..098fe0db 100644 --- a/packages/hypergraph-react/src/publish-ops.ts +++ b/packages/hypergraph-react/src/publish-ops.ts @@ -1,6 +1,6 @@ import type { Op } from '@graphprotocol/grc-20'; -import { Graph, Ipfs } from '@graphprotocol/grc-20'; -import { Connect } from '@graphprotocol/hypergraph'; +import { Ipfs } from '@graphprotocol/grc-20'; +import { Config, Connect } from '@graphprotocol/hypergraph'; import type { Hash } from 'viem'; type PublishParams = { @@ -34,7 +34,7 @@ export const publishOps = async ({ name, ops, walletClient, space }: PublishPara const cid = publishResult.cid; // This returns the correct contract address and calldata depending on the space id - const result = await fetch(`${Graph.TESTNET_API_ORIGIN}/space/${space}/edit/calldata`, { + const result = await fetch(`${Config.getApiOrigin()}/space/${space}/edit/calldata`, { method: 'POST', body: JSON.stringify({ cid }), }); diff --git a/packages/hypergraph-react/test/prepare-publish.test.ts b/packages/hypergraph-react/test/prepare-publish.test.ts index db5ac002..434d7bf7 100644 --- a/packages/hypergraph-react/test/prepare-publish.test.ts +++ b/packages/hypergraph-react/test/prepare-publish.test.ts @@ -1,6 +1,6 @@ import { Repo } from '@automerge/automerge-repo'; import { Graph, Id } from '@graphprotocol/grc-20'; -import { Entity, store, Type } from '@graphprotocol/hypergraph'; +import { Config, Entity, store, Type } from '@graphprotocol/hypergraph'; import '@testing-library/jest-dom/vitest'; import request from 'graphql-request'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -133,7 +133,7 @@ describe('preparePublish', () => { const result = await preparePublish(params); - expect(mockRequest).toHaveBeenCalledWith(`${Graph.TESTNET_API_ORIGIN}/v2/graphql`, expect.any(String), { + expect(mockRequest).toHaveBeenCalledWith(`${Config.getApiOrigin()}/v2/graphql`, expect.any(String), { entityId: entity.id, spaceId: publicSpaceId, }); diff --git a/packages/hypergraph/src/config.ts b/packages/hypergraph/src/config.ts new file mode 100644 index 00000000..36238fa3 --- /dev/null +++ b/packages/hypergraph/src/config.ts @@ -0,0 +1,23 @@ +import { Graph } from '@graphprotocol/grc-20'; + +let apiOrigin: string | null = null; + +/** + * Sets the API origin globally for all hypergraph API calls. + * @param origin - The API origin URL (e.g., "https://api.mainnet.graphprotocol.io") + */ +export const setApiOrigin = (origin: string) => { + apiOrigin = origin; +}; + +/** + * Gets the configured API origin, or defaults to Graph.TESTNET_API_ORIGIN if not set. + * @returns The API origin URL + */ +export const getApiOrigin = (): string => { + if (apiOrigin) { + return apiOrigin; + } + // Default to testnet + return Graph.TESTNET_API_ORIGIN; +}; diff --git a/packages/hypergraph/src/entity/find-many-public.ts b/packages/hypergraph/src/entity/find-many-public.ts index 73e7ee05..992ce4c8 100644 --- a/packages/hypergraph/src/entity/find-many-public.ts +++ b/packages/hypergraph/src/entity/find-many-public.ts @@ -1,5 +1,4 @@ -import { Graph } from '@graphprotocol/grc-20'; -import { Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; +import { Config, Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; import * as Either from 'effect/Either'; import * as Option from 'effect/Option'; import type * as ParseResult from 'effect/ParseResult'; @@ -296,11 +295,7 @@ export const findManyPublic = async < queryVariables.sortDirection = sortDirection; } - const result = await request( - `${Graph.TESTNET_API_ORIGIN}/v2/graphql`, - queryDocument, - queryVariables, - ); + const result = await request(`${Config.getApiOrigin()}/v2/graphql`, queryDocument, queryVariables); const { data, invalidEntities, invalidRelationEntities } = parseResult( result, diff --git a/packages/hypergraph/src/entity/find-one-public.ts b/packages/hypergraph/src/entity/find-one-public.ts index ea5a8723..f52d0537 100644 --- a/packages/hypergraph/src/entity/find-one-public.ts +++ b/packages/hypergraph/src/entity/find-one-public.ts @@ -1,5 +1,4 @@ -import { Graph } from '@graphprotocol/grc-20'; -import { Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; +import { Config, Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; import * as Either from 'effect/Either'; import * as Option from 'effect/Option'; import type * as ParseResult from 'effect/ParseResult'; @@ -165,7 +164,7 @@ export const findOnePublic = async < const queryDocument = buildEntityQuery(relationTypeIds, includeSpaceIds); - const result = await request(`${Graph.TESTNET_API_ORIGIN}/v2/graphql`, queryDocument, { + const result = await request(`${Config.getApiOrigin()}/v2/graphql`, queryDocument, { id, spaceId: space, }); diff --git a/packages/hypergraph/src/entity/search-many-public.ts b/packages/hypergraph/src/entity/search-many-public.ts index ec78b37f..5f3e924e 100644 --- a/packages/hypergraph/src/entity/search-many-public.ts +++ b/packages/hypergraph/src/entity/search-many-public.ts @@ -1,5 +1,4 @@ -import { Graph } from '@graphprotocol/grc-20'; -import { Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; +import { Config, Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; import * as Option from 'effect/Option'; import type * as Schema from 'effect/Schema'; import * as SchemaAST from 'effect/SchemaAST'; @@ -82,7 +81,7 @@ export const searchManyPublic = async < const filterParams = filter ? Utils.translateFilterToGraphql(filter, type) : {}; - const result = await request(`${Graph.TESTNET_API_ORIGIN}/v2/graphql`, queryDocument, { + const result = await request(`${Config.getApiOrigin()}/v2/graphql`, queryDocument, { spaceId: space, typeIds, query, diff --git a/packages/hypergraph/src/index.ts b/packages/hypergraph/src/index.ts index b5f45ec4..ae130576 100644 --- a/packages/hypergraph/src/index.ts +++ b/packages/hypergraph/src/index.ts @@ -1,5 +1,6 @@ export { Id } from '@graphprotocol/grc-20'; export * as Typesync from './cli/services/Model.js'; +export * as Config from './config.js'; export * as Connect from './connect/index.js'; export * as Constants from './constants.js'; export * as Entity from './entity/index.js'; diff --git a/packages/hypergraph/src/space/find-many-public.ts b/packages/hypergraph/src/space/find-many-public.ts index 0cfc192c..bf5ad79f 100644 --- a/packages/hypergraph/src/space/find-many-public.ts +++ b/packages/hypergraph/src/space/find-many-public.ts @@ -1,4 +1,5 @@ -import { ContentIds, Graph, SystemIds } from '@graphprotocol/grc-20'; +import { ContentIds, SystemIds } from '@graphprotocol/grc-20'; +import { Config } from '@graphprotocol/hypergraph'; import * as Either from 'effect/Either'; import * as EffectSchema from 'effect/Schema'; import { request } from 'graphql-request'; @@ -131,7 +132,7 @@ export const findManyPublic = async (params?: FindManyPublicParams) => { throw new Error('Provide only one of memberAccountAddress or editorAccountAddress when calling findManyPublic().'); } - const endpoint = `${Graph.TESTNET_API_ORIGIN}/v2/graphql`; + const endpoint = `${Config.getApiOrigin()}/v2/graphql`; if (memberAccountAddress) { const queryResult = await request(endpoint, memberSpacesQueryDocument, { diff --git a/packages/typesync-studio/src/Components/Schema/AppSchemaSpaceDialog.tsx b/packages/typesync-studio/src/Components/Schema/AppSchemaSpaceDialog.tsx index e5447667..ea15501f 100644 --- a/packages/typesync-studio/src/Components/Schema/AppSchemaSpaceDialog.tsx +++ b/packages/typesync-studio/src/Components/Schema/AppSchemaSpaceDialog.tsx @@ -154,7 +154,6 @@ type SchemaSpaceSelectProps = { | { id: string; name: string | undefined; - spaceAddress: string; }, ): void; };