From 68f1d5fc9004c07a759a3fc2a8368f0dc1ff3e52 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Fri, 14 Nov 2025 17:40:47 +0100 Subject: [PATCH] add useEntitiesPublicInfinite hook --- .changeset/beige-lemons-beg.md | 6 +++ apps/events/src/routeTree.gen.ts | 23 +++++++++ apps/events/src/routes/__root.tsx | 3 ++ .../src/routes/podcasts-infinite.lazy.tsx | 48 +++++++++++++++++++ apps/events/src/routes/podcasts.lazy.tsx | 3 +- .../src/hooks/use-entities-public-infinite.ts | 45 +++++++++++++++++ packages/hypergraph-react/src/index.ts | 1 + .../hypergraph-react/src/internal/types.ts | 2 +- 8 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 .changeset/beige-lemons-beg.md create mode 100644 apps/events/src/routes/podcasts-infinite.lazy.tsx create mode 100644 packages/hypergraph-react/src/hooks/use-entities-public-infinite.ts diff --git a/.changeset/beige-lemons-beg.md b/.changeset/beige-lemons-beg.md new file mode 100644 index 00000000..360e4c40 --- /dev/null +++ b/.changeset/beige-lemons-beg.md @@ -0,0 +1,6 @@ +--- +"@graphprotocol/hypergraph-react": patch +--- + +add useEntitiesPublicInfinite hook] + \ No newline at end of file diff --git a/apps/events/src/routeTree.gen.ts b/apps/events/src/routeTree.gen.ts index fe31d430..2e315526 100644 --- a/apps/events/src/routeTree.gen.ts +++ b/apps/events/src/routeTree.gen.ts @@ -23,10 +23,18 @@ import { Route as SpaceSpaceIdPlaygroundRouteImport } from './routes/space/$spac import { Route as SpaceSpaceIdEventsRouteImport } from './routes/space/$spaceId/events' import { Route as SpaceSpaceIdChatRouteImport } from './routes/space/$spaceId/chat' +const PodcastsInfiniteLazyRouteImport = createFileRoute('/podcasts-infinite')() const PodcastsLazyRouteImport = createFileRoute('/podcasts')() const PlaygroundLazyRouteImport = createFileRoute('/playground')() const LoginLazyRouteImport = createFileRoute('/login')() +const PodcastsInfiniteLazyRoute = PodcastsInfiniteLazyRouteImport.update({ + id: '/podcasts-infinite', + path: '/podcasts-infinite', + getParentRoute: () => rootRouteImport, +} as any).lazy(() => + import('./routes/podcasts-infinite.lazy').then((d) => d.Route), +) const PodcastsLazyRoute = PodcastsLazyRouteImport.update({ id: '/podcasts', path: '/podcasts', @@ -105,6 +113,7 @@ export interface FileRoutesByFullPath { '/login': typeof LoginLazyRoute '/playground': typeof PlaygroundLazyRoute '/podcasts': typeof PodcastsLazyRoute + '/podcasts-infinite': typeof PodcastsInfiniteLazyRoute '/account-inbox/$inboxId': typeof AccountInboxInboxIdRoute '/friends/$accountAddress': typeof FriendsAccountAddressRoute '/space/$spaceId': typeof SpaceSpaceIdRouteWithChildren @@ -121,6 +130,7 @@ export interface FileRoutesByTo { '/login': typeof LoginLazyRoute '/playground': typeof PlaygroundLazyRoute '/podcasts': typeof PodcastsLazyRoute + '/podcasts-infinite': typeof PodcastsInfiniteLazyRoute '/account-inbox/$inboxId': typeof AccountInboxInboxIdRoute '/friends/$accountAddress': typeof FriendsAccountAddressRoute '/space/$spaceId/chat': typeof SpaceSpaceIdChatRoute @@ -137,6 +147,7 @@ export interface FileRoutesById { '/login': typeof LoginLazyRoute '/playground': typeof PlaygroundLazyRoute '/podcasts': typeof PodcastsLazyRoute + '/podcasts-infinite': typeof PodcastsInfiniteLazyRoute '/account-inbox/$inboxId': typeof AccountInboxInboxIdRoute '/friends/$accountAddress': typeof FriendsAccountAddressRoute '/space/$spaceId': typeof SpaceSpaceIdRouteWithChildren @@ -155,6 +166,7 @@ export interface FileRouteTypes { | '/login' | '/playground' | '/podcasts' + | '/podcasts-infinite' | '/account-inbox/$inboxId' | '/friends/$accountAddress' | '/space/$spaceId' @@ -171,6 +183,7 @@ export interface FileRouteTypes { | '/login' | '/playground' | '/podcasts' + | '/podcasts-infinite' | '/account-inbox/$inboxId' | '/friends/$accountAddress' | '/space/$spaceId/chat' @@ -186,6 +199,7 @@ export interface FileRouteTypes { | '/login' | '/playground' | '/podcasts' + | '/podcasts-infinite' | '/account-inbox/$inboxId' | '/friends/$accountAddress' | '/space/$spaceId' @@ -203,6 +217,7 @@ export interface RootRouteChildren { LoginLazyRoute: typeof LoginLazyRoute PlaygroundLazyRoute: typeof PlaygroundLazyRoute PodcastsLazyRoute: typeof PodcastsLazyRoute + PodcastsInfiniteLazyRoute: typeof PodcastsInfiniteLazyRoute AccountInboxInboxIdRoute: typeof AccountInboxInboxIdRoute FriendsAccountAddressRoute: typeof FriendsAccountAddressRoute SpaceSpaceIdRoute: typeof SpaceSpaceIdRouteWithChildren @@ -210,6 +225,13 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/podcasts-infinite': { + id: '/podcasts-infinite' + path: '/podcasts-infinite' + fullPath: '/podcasts-infinite' + preLoaderRoute: typeof PodcastsInfiniteLazyRouteImport + parentRoute: typeof rootRouteImport + } '/podcasts': { id: '/podcasts' path: '/podcasts' @@ -339,6 +361,7 @@ const rootRouteChildren: RootRouteChildren = { LoginLazyRoute: LoginLazyRoute, PlaygroundLazyRoute: PlaygroundLazyRoute, PodcastsLazyRoute: PodcastsLazyRoute, + PodcastsInfiniteLazyRoute: PodcastsInfiniteLazyRoute, AccountInboxInboxIdRoute: AccountInboxInboxIdRoute, FriendsAccountAddressRoute: FriendsAccountAddressRoute, SpaceSpaceIdRoute: SpaceSpaceIdRouteWithChildren, diff --git a/apps/events/src/routes/__root.tsx b/apps/events/src/routes/__root.tsx index ed2ee452..282f24fe 100644 --- a/apps/events/src/routes/__root.tsx +++ b/apps/events/src/routes/__root.tsx @@ -36,6 +36,9 @@ export const Route = createRootRoute({ Podcasts + + Podcasts Infinite Query + Playground diff --git a/apps/events/src/routes/podcasts-infinite.lazy.tsx b/apps/events/src/routes/podcasts-infinite.lazy.tsx new file mode 100644 index 00000000..d2874abb --- /dev/null +++ b/apps/events/src/routes/podcasts-infinite.lazy.tsx @@ -0,0 +1,48 @@ +import { useEntitiesPublicInfinite } from '@graphprotocol/hypergraph-react'; +import { createLazyFileRoute } from '@tanstack/react-router'; +import { useEffect } from 'react'; +import { Podcast } from '@/schema'; + +export const Route = createLazyFileRoute('/podcasts-infinite')({ + component: RouteComponent, +}); + +function RouteComponent() { + const space = 'e252f9e1-d3ad-4460-8bf1-54f93b02f220'; + + const { data, isLoading, isError, fetchNextPage } = useEntitiesPublicInfinite(Podcast, { + first: 2, + offset: 0, + space: space, + include: { + projects: {}, + }, + }); + + useEffect(() => { + setTimeout(() => { + fetchNextPage(); + setTimeout(() => { + fetchNextPage(); + }, 1000); + }, 1000); + }, [fetchNextPage]); + console.log({ data, isLoading, isError }); + + return ( + <> +

Podcasts

+ {isLoading &&
Loading...
} + {isError &&
Error
} + {data?.pages.map((page) => { + return page.data.map((podcast) => ( +
+

{podcast.name}

+
+
+ )); + })} + {/*
{JSON.stringify(data, null, 2)}
*/} + + ); +} diff --git a/apps/events/src/routes/podcasts.lazy.tsx b/apps/events/src/routes/podcasts.lazy.tsx index 84a937e2..e5556256 100644 --- a/apps/events/src/routes/podcasts.lazy.tsx +++ b/apps/events/src/routes/podcasts.lazy.tsx @@ -11,8 +11,7 @@ function RouteComponent() { const { data, isLoading, isError } = useEntities(Podcast, { mode: 'public', - first: 6, - offset: 0, + first: 100, space: space, include: { projects: {}, diff --git a/packages/hypergraph-react/src/hooks/use-entities-public-infinite.ts b/packages/hypergraph-react/src/hooks/use-entities-public-infinite.ts new file mode 100644 index 00000000..ed77d5bc --- /dev/null +++ b/packages/hypergraph-react/src/hooks/use-entities-public-infinite.ts @@ -0,0 +1,45 @@ +import { Constants, Entity, Utils } from '@graphprotocol/hypergraph'; +import { useInfiniteQuery as useInfiniteQueryTanstack } from '@tanstack/react-query'; +import * as Option from 'effect/Option'; +import type * as Schema from 'effect/Schema'; +import * as SchemaAST from 'effect/SchemaAST'; +import type { QueryPublicParams } from '../internal/types.js'; +import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js'; + +export const useEntitiesPublicInfinite = ( + type: S, + params?: QueryPublicParams, +) => { + const { enabled = true, filter, include, space: spaceFromParams, first = 2, offset = 0 } = params ?? {}; + const { space: spaceFromContext } = useHypergraphSpaceInternal(); + const space = spaceFromParams ?? spaceFromContext; + + // constructing the relation type ids for the query + const relationTypeIds = Utils.getRelationTypeIds(type, include); + + const typeIds = SchemaAST.getAnnotation(Constants.TypeIdsSymbol)(type.ast as SchemaAST.TypeLiteral).pipe( + Option.getOrElse(() => []), + ); + + const result = useInfiniteQueryTanstack({ + queryKey: [ + 'hypergraph-public-entities', + space, + typeIds, + relationTypeIds.level1, + relationTypeIds.level2, + filter, + 'infinite', + ], + queryFn: async ({ pageParam }) => { + return Entity.findManyPublic(type, { filter, include, space, first, offset: pageParam }); + }, + getNextPageParam: (_lastPage, pages) => { + return offset + pages.length * first; + }, + initialPageParam: offset, + enabled, + }); + + return result; +}; diff --git a/packages/hypergraph-react/src/index.ts b/packages/hypergraph-react/src/index.ts index 40195e93..66647031 100644 --- a/packages/hypergraph-react/src/index.ts +++ b/packages/hypergraph-react/src/index.ts @@ -8,6 +8,7 @@ export { HypergraphSpaceProvider } from './HypergraphSpaceContext.js'; export { useCreateEntity } from './hooks/use-create-entity.js'; export { useDeleteEntity } from './hooks/use-delete-entity.js'; export { useEntities } from './hooks/use-entities.js'; +export { useEntitiesPublicInfinite } from './hooks/use-entities-public-infinite.js'; export { useEntity } from './hooks/use-entity.js'; export { useHardDeleteEntity } from './hooks/use-hard-delete-entity.js'; export { usePrivyAuthCreatePrivateSpace as _usePrivyAuthCreatePrivateSpace } from './hooks/use-privy-auth-create-private-space.js'; diff --git a/packages/hypergraph-react/src/internal/types.ts b/packages/hypergraph-react/src/internal/types.ts index fce1ca2d..2c0f1c5d 100644 --- a/packages/hypergraph-react/src/internal/types.ts +++ b/packages/hypergraph-react/src/internal/types.ts @@ -2,7 +2,7 @@ import type { Entity } from '@graphprotocol/hypergraph'; import type * as Schema from 'effect/Schema'; export type QueryPublicParams = { - enabled: boolean; + enabled?: boolean | undefined; filter?: Entity.EntityFilter> | undefined; // TODO: for multi-level nesting it should only allow the allowed properties instead of Record> include?: { [K in keyof Schema.Schema.Type]?: Record> } | undefined;