Skip to content

Commit 56f49c4

Browse files
nikgrafCopilot
andauthored
improve handling invalid entities (#565)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 531420e commit 56f49c4

23 files changed

+515
-83
lines changed

.changeset/eager-boxes-wash.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/hypergraph-react": minor
3+
"@graphprotocol/hypergraph": minor
4+
---
5+
6+
Change behavior of entity validation by filtering out invalid entities of relations and adding them to `invalidRelationEntities`
7+

.changeset/loose-candles-attack.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/hypergraph-react": minor
3+
"@graphprotocol/hypergraph": minor
4+
---
5+
6+
enrich `invalidEntities` to expose both the raw payload and decode error for each invalid entity
7+

.changeset/rotten-animals-hunt.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/hypergraph-react": minor
3+
"@graphprotocol/hypergraph": minor
4+
---
5+
6+
Entity.findOnePublic will return { entity: null, invalidEntity: { raw: {…}, error: … } } instead of throwing an error in case it's an invalid entity
7+

.changeset/shaky-guests-report.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/hypergraph-react": patch
3+
"@graphprotocol/hypergraph": patch
4+
---
5+
6+
Add a logInvalidResults toggle to `Entity.findOnePublic/findManyPublic`, plus pass-through support in the React provider and hooks, so apps can selectively silence or surface schema-validation warnings while still receiving the invalid payload lists.
7+

apps/events/src/routes/podcasts.lazy.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEntities, usePublicSpaces } from '@graphprotocol/hypergraph-react';
1+
import { useEntities, useEntity, usePublicSpaces } from '@graphprotocol/hypergraph-react';
22
import { createLazyFileRoute } from '@tanstack/react-router';
33
import { Podcast, Topic } from '@/schema';
44

@@ -22,19 +22,23 @@ function RouteComponent() {
2222
// }, 1000);
2323
// }, []);
2424

25-
// const { data: podcast } = useEntity(Podcast, {
26-
// id: 'f5d27d3e-3a51-452d-bac2-702574381633',
27-
// mode: 'public',
28-
// space: space,
29-
// include: {
30-
// listenOn: {},
31-
// hosts: {
32-
// avatar: {},
33-
// },
34-
// episodes: {},
35-
// },
36-
// });
37-
// console.log({ podcast });
25+
const {
26+
data: podcast,
27+
invalidEntity,
28+
invalidRelationEntities,
29+
} = useEntity(Podcast, {
30+
id: 'f5d27d3e-3a51-452d-bac2-702574381633',
31+
mode: 'public',
32+
space: space,
33+
include: {
34+
listenOn: {},
35+
hosts: {
36+
avatar: {},
37+
},
38+
episodes: {},
39+
},
40+
});
41+
console.log({ podcast, invalidEntity, invalidRelationEntities });
3842

3943
const { data, isLoading, isError } = useEntities(Podcast, {
4044
mode: 'public',

docs/docs/query-private-data.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Please learn more about filtering in the [Filtering query results](#filtering-qu
5050
useEntities for private data returns:
5151

5252
- data - a list of entities defined in your schema
53-
- invalidEntities - a list of entities that are in your space storage with correct type, but can't be parsed to your schema
53+
- invalidEntities - each entry contains the invalid raw payload (`raw`) alongside the decode `error`
5454
- deleted - a list of entities that are marked as deleted, we keep them around to be able to later be able to publish the deleted information to the public knowledge graph
5555

5656
```ts

docs/docs/query-public-data.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,18 @@ Please learn more about filtering in the [Filtering query results](#filtering-qu
8383

8484
### Returned data
8585

86-
useEntities for private data returns:
86+
useEntities for public data returns:
8787

8888
- data - a list of entities defined in your schema
89-
- invalidEntities - a list of entities that are in your space storage with correct type, but can't be parsed to your schema
89+
- invalidEntities - each entry includes the invalid raw payload (`raw`) plus the corresponding `error` explaining why decoding failed
90+
- invalidRelationEntities - each entry includes the invalid raw payload (`raw`) plus the corresponding `error` explaining why decoding failed
9091
- isPending - a boolean indicating if the query is loading
9192
- isError - a boolean indicating if the query failed
9293

9394
In addition you have access to the full response from `@tanstack/react-query`'s `useQuery` hook, which is used internally to query the public data.
9495

9596
```ts
96-
const { data, isPending, isError } = useEntities(Event, { mode: 'public' });
97+
const { data, invalidEntities, invalidRelationEntities, isPending, isError } = useEntities(Event, { mode: 'public' });
9798
```
9899

99100
## Fetching a single public entity
@@ -106,7 +107,7 @@ When you only need a single entity—for example to power a detail page—you ca
106107
import { useEntity } from '@graphprotocol/hypergraph-react';
107108
import { Project } from '../schema';
108109

109-
const { data: project, isPending, isError } = useEntity(Project, {
110+
const { data: project, invalidEntity, invalidRelationEntities, isPending, isError } = useEntity(Project, {
110111
id: '9f130661-8c3f-4db7-9bdc-3ce69631c5ef',
111112
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
112113
mode: 'public',

packages/hypergraph-react/src/HypergraphAppContext.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export type HypergraphAppCtx = {
124124
error: string;
125125
};
126126
syncServerUri: string;
127+
logInvalidResults: boolean;
127128
};
128129

129130
export const HypergraphAppContext = createContext<HypergraphAppCtx>({
@@ -209,6 +210,7 @@ export const HypergraphAppContext = createContext<HypergraphAppCtx>({
209210
throw new Error('processConnectAuthSuccess is missing');
210211
},
211212
syncServerUri: '',
213+
logInvalidResults: true,
212214
});
213215

214216
export function useHypergraphApp() {
@@ -229,6 +231,7 @@ export type HypergraphAppProviderProps = Readonly<{
229231
chainId?: number;
230232
children: ReactNode;
231233
appId: string;
234+
logInvalidResults?: boolean;
232235
}>;
233236

234237
const mockStorage = {
@@ -245,6 +248,7 @@ export function HypergraphAppProvider({
245248
chainId = Connect.GEO_TESTNET.id,
246249
appId,
247250
children,
251+
logInvalidResults = true,
248252
}: HypergraphAppProviderProps) {
249253
const [websocketConnection, setWebsocketConnection] = useState<WebSocket>();
250254
const [isConnecting, setIsConnecting] = useState(true);
@@ -1567,6 +1571,7 @@ export function HypergraphAppProvider({
15671571
redirectToConnect: redirectToConnectForContext,
15681572
processConnectAuthSuccess: processConnectAuthSuccessForContext,
15691573
syncServerUri,
1574+
logInvalidResults,
15701575
}}
15711576
>
15721577
<QueryClientProvider client={queryClient}>

packages/hypergraph-react/src/hooks/use-entities-public-infinite.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,27 @@ import { useInfiniteQuery as useInfiniteQueryTanstack } from '@tanstack/react-qu
33
import * as Option from 'effect/Option';
44
import type * as Schema from 'effect/Schema';
55
import * as SchemaAST from 'effect/SchemaAST';
6+
import { useHypergraphApp } from '../HypergraphAppContext.js';
67
import type { QueryPublicParams } from '../internal/types.js';
78
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
89

910
export const useEntitiesPublicInfinite = <S extends Schema.Schema.AnyNoContext>(
1011
type: S,
1112
params?: QueryPublicParams<S>,
1213
) => {
13-
const { enabled = true, filter, include, space: spaceFromParams, spaces, first = 2, offset = 0 } = params ?? {};
14+
const {
15+
enabled = true,
16+
filter,
17+
include,
18+
space: spaceFromParams,
19+
spaces,
20+
first = 2,
21+
offset = 0,
22+
logInvalidResults: logInvalidResultsParam,
23+
} = params ?? {};
1424
const { space: spaceFromContext } = useHypergraphSpaceInternal();
25+
const { logInvalidResults: contextLogInvalidResults = true } = useHypergraphApp();
26+
const logInvalidResults = logInvalidResultsParam ?? contextLogInvalidResults ?? true;
1527
const space = spaceFromParams ?? spaceFromContext;
1628
const spaceSelectionKey = spaces ?? space;
1729
const typeIds = SchemaAST.getAnnotation<string[]>(Constants.TypeIdsSymbol)(type.ast as SchemaAST.TypeLiteral).pipe(
@@ -27,6 +39,7 @@ export const useEntitiesPublicInfinite = <S extends Schema.Schema.AnyNoContext>(
2739
...(spaces ? { spaces } : { space }),
2840
first,
2941
offset: pageParam,
42+
logInvalidResults,
3043
});
3144
},
3245
getNextPageParam: (_lastPage, pages) => {

packages/hypergraph-react/src/hooks/use-entities.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Entity } from '@graphprotocol/hypergraph';
22
import type * as Schema from 'effect/Schema';
3+
import { useHypergraphApp } from '../HypergraphAppContext.js';
34
import { useEntitiesPrivate } from '../internal/use-entities-private.js';
45
import { useEntitiesPublic } from '../internal/use-entities-public.js';
56
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
@@ -26,11 +27,25 @@ type UseEntitiesParams<S extends Schema.Schema.AnyNoContext> = (
2627
}
2728
| undefined;
2829
backlinksTotalCountsTypeId1?: string | undefined;
30+
logInvalidResults?: boolean;
2931
};
3032

3133
export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S, params: UseEntitiesParams<S>) {
32-
const { mode, filter, include, space, spaces, first, offset, orderBy, backlinksTotalCountsTypeId1 } = params;
34+
const {
35+
mode,
36+
filter,
37+
include,
38+
space,
39+
spaces,
40+
first,
41+
offset,
42+
orderBy,
43+
backlinksTotalCountsTypeId1,
44+
logInvalidResults: logInvalidResultsParam,
45+
} = params;
3346
const { space: spaceFromContext } = useHypergraphSpaceInternal();
47+
const { logInvalidResults: contextLogInvalidResults = true } = useHypergraphApp();
48+
const logInvalidResults = logInvalidResultsParam ?? contextLogInvalidResults ?? true;
3449
const resolvedSpace = space ?? spaceFromContext;
3550
const publicSpaceParams = spaces ? { spaces } : { space: resolvedSpace };
3651
const publicResult = useEntitiesPublic(type, {
@@ -42,6 +57,7 @@ export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S,
4257
orderBy,
4358
backlinksTotalCountsTypeId1,
4459
...publicSpaceParams,
60+
logInvalidResults,
4561
});
4662
const localResult = useEntitiesPrivate(type, { enabled: mode === 'private', filter, include, space: resolvedSpace });
4763

0 commit comments

Comments
 (0)