Skip to content

Commit 8c2c42b

Browse files
authored
add findOnePublic (#555)
1 parent 06daec1 commit 8c2c42b

4 files changed

Lines changed: 325 additions & 1 deletion

File tree

.changeset/public-bugs-ask.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/hypergraph": patch
3+
"@graphprotocol/hypergraph-react": patch
4+
---
5+
6+
add Entity.findOnePublic
7+

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { Entity } from '@graphprotocol/hypergraph';
12
import { useEntities } from '@graphprotocol/hypergraph-react';
23
import { createLazyFileRoute } from '@tanstack/react-router';
4+
import { useEffect } from 'react';
35
import { Podcast } from '@/schema';
46

57
export const Route = createLazyFileRoute('/podcasts')({
@@ -9,6 +11,19 @@ export const Route = createLazyFileRoute('/podcasts')({
911
function RouteComponent() {
1012
const space = 'e252f9e1-d3ad-4460-8bf1-54f93b02f220';
1113

14+
useEffect(() => {
15+
setTimeout(async () => {
16+
const result = await Entity.findOnePublic(Podcast, {
17+
id: 'f5d27d3e-3a51-452d-bac2-702574381633',
18+
space: space,
19+
include: {
20+
projects: {},
21+
},
22+
});
23+
console.log('findOnePublic result:', result);
24+
}, 1000);
25+
}, []);
26+
1227
const { data, isLoading, isError } = useEntities(Podcast, {
1328
mode: 'public',
1429
first: 100,
@@ -28,7 +43,7 @@ function RouteComponent() {
2843
{data?.map((podcast) => (
2944
<div key={podcast.id}>
3045
<h2>
31-
{podcast.backlinksTotalCountsTypeId1} - {podcast.dateFounded.toISOString()} {podcast.name}
46+
{podcast.backlinksTotalCountsTypeId1} - {podcast.dateFounded.toISOString()} {podcast.name} - {podcast.id}
3247
</h2>
3348
{podcast.projects.map((project) => (
3449
<div key={project._relation.id}>
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import { Graph } from '@graphprotocol/grc-20';
2+
import { Constants, type Entity, Utils } from '@graphprotocol/hypergraph';
3+
import * as Either from 'effect/Either';
4+
import * as Option from 'effect/Option';
5+
import * as Schema from 'effect/Schema';
6+
import * as SchemaAST from 'effect/SchemaAST';
7+
import { gql, request } from 'graphql-request';
8+
9+
type EntityQueryResult = {
10+
entity: {
11+
id: string;
12+
name: string;
13+
valuesList: {
14+
propertyId: string;
15+
string: string;
16+
boolean: boolean;
17+
number: number;
18+
time: string;
19+
point: string;
20+
}[];
21+
relationsList?: {
22+
id: string;
23+
entity: {
24+
valuesList: {
25+
propertyId: string;
26+
string: string;
27+
boolean: boolean;
28+
number: number;
29+
time: string;
30+
point: string;
31+
}[];
32+
};
33+
toEntity: {
34+
id: string;
35+
name: string;
36+
valuesList: {
37+
propertyId: string;
38+
string: string;
39+
boolean: boolean;
40+
number: number;
41+
time: string;
42+
point: string;
43+
}[];
44+
relationsList?: {
45+
id: string;
46+
entity: {
47+
valuesList: {
48+
propertyId: string;
49+
string: string;
50+
boolean: boolean;
51+
number: number;
52+
time: string;
53+
point: string;
54+
}[];
55+
};
56+
toEntity: {
57+
id: string;
58+
name: string;
59+
valuesList: {
60+
propertyId: string;
61+
string: string;
62+
boolean: boolean;
63+
number: number;
64+
time: string;
65+
point: string;
66+
}[];
67+
};
68+
typeId: string;
69+
}[];
70+
};
71+
typeId: string;
72+
}[];
73+
} | null;
74+
};
75+
76+
export type FindOnePublicParams<S extends Schema.Schema.AnyNoContext> = {
77+
id: string;
78+
space: string;
79+
// TODO: for multi-level nesting it should only allow the allowed properties instead of Record<string, Record<string, never>>
80+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
81+
};
82+
83+
const entityQueryDocumentLevel0 = gql`
84+
query entity($id: UUID!, $spaceId: UUID!) {
85+
entity(
86+
id: $id,
87+
) {
88+
id
89+
name
90+
valuesList(filter: {spaceId: {is: $spaceId}}) {
91+
propertyId
92+
string
93+
boolean
94+
number
95+
time
96+
point
97+
}
98+
}
99+
}
100+
`;
101+
102+
const entityQueryDocumentLevel1 = gql`
103+
query entity($id: UUID!, $spaceId: UUID!, $relationTypeIdsLevel1: [UUID!]!) {
104+
entity(
105+
id: $id,
106+
) {
107+
id
108+
name
109+
valuesList(filter: {spaceId: {is: $spaceId}}) {
110+
propertyId
111+
string
112+
boolean
113+
number
114+
time
115+
point
116+
}
117+
relationsList(
118+
filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}},
119+
) {
120+
id
121+
entity {
122+
valuesList(filter: {spaceId: {is: $spaceId}}) {
123+
propertyId
124+
string
125+
boolean
126+
number
127+
time
128+
point
129+
}
130+
}
131+
toEntity {
132+
id
133+
name
134+
valuesList(filter: {spaceId: {is: $spaceId}}) {
135+
propertyId
136+
string
137+
boolean
138+
number
139+
time
140+
point
141+
}
142+
}
143+
typeId
144+
}
145+
}
146+
}
147+
`;
148+
149+
const entityQueryDocumentLevel2 = gql`
150+
query entity($id: UUID!, $spaceId: UUID!, $relationTypeIdsLevel1: [UUID!]!, $relationTypeIdsLevel2: [UUID!]!) {
151+
entity(
152+
id: $id,
153+
) {
154+
id
155+
name
156+
valuesList(filter: {spaceId: {is: $spaceId}}) {
157+
propertyId
158+
string
159+
boolean
160+
number
161+
time
162+
point
163+
}
164+
relationsList(
165+
filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}},
166+
) {
167+
id
168+
entity {
169+
valuesList(filter: {spaceId: {is: $spaceId}}) {
170+
propertyId
171+
string
172+
boolean
173+
number
174+
time
175+
point
176+
}
177+
}
178+
toEntity {
179+
id
180+
name
181+
valuesList(filter: {spaceId: {is: $spaceId}}) {
182+
propertyId
183+
string
184+
boolean
185+
number
186+
time
187+
point
188+
}
189+
relationsList(
190+
filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel2}},
191+
) {
192+
id
193+
entity {
194+
valuesList(filter: {spaceId: {is: $spaceId}}) {
195+
propertyId
196+
string
197+
boolean
198+
number
199+
time
200+
point
201+
}
202+
}
203+
toEntity {
204+
id
205+
name
206+
valuesList(filter: {spaceId: {is: $spaceId}}) {
207+
propertyId
208+
string
209+
boolean
210+
number
211+
time
212+
point
213+
}
214+
}
215+
typeId
216+
}
217+
}
218+
typeId
219+
}
220+
}
221+
}
222+
`;
223+
224+
const parseResult = <S extends Schema.Schema.AnyNoContext>(queryData: EntityQueryResult, type: S) => {
225+
if (!queryData.entity) {
226+
return null;
227+
}
228+
229+
const schemaWithId = Utils.addIdSchemaField(type);
230+
const decode = Schema.decodeUnknownEither(schemaWithId);
231+
const queryEntity = queryData.entity;
232+
let rawEntity: Record<string, string | boolean | number | unknown[] | Date> = {
233+
id: queryEntity.id,
234+
};
235+
236+
const ast = type.ast as SchemaAST.TypeLiteral;
237+
238+
for (const prop of ast.propertySignatures) {
239+
const propType =
240+
prop.isOptional && SchemaAST.isUnion(prop.type)
241+
? (prop.type.types.find((member) => !SchemaAST.isUndefinedKeyword(member)) ?? prop.type)
242+
: prop.type;
243+
244+
const result = SchemaAST.getAnnotation<string>(Constants.PropertyIdSymbol)(propType);
245+
246+
if (Option.isSome(result)) {
247+
const value = queryEntity.valuesList.find((a) => a.propertyId === result.value);
248+
if (value) {
249+
const rawValue = Utils.convertPropertyValue(value, propType);
250+
if (rawValue) {
251+
rawEntity[String(prop.name)] = rawValue;
252+
}
253+
}
254+
}
255+
}
256+
257+
// @ts-expect-error
258+
rawEntity = {
259+
...rawEntity,
260+
...Utils.convertRelations(queryEntity, ast),
261+
};
262+
263+
const decodeResult = decode({
264+
...rawEntity,
265+
__deleted: false,
266+
});
267+
268+
if (Either.isRight(decodeResult)) {
269+
return { ...decodeResult.right } as Entity.Entity<S>;
270+
}
271+
272+
if (process.env.NODE_ENV !== 'production') {
273+
console.warn('Invalid entity', rawEntity);
274+
}
275+
throw new Error('Invalid entity');
276+
};
277+
278+
export const findOnePublic = async <S extends Schema.Schema.AnyNoContext>(type: S, params: FindOnePublicParams<S>) => {
279+
const { id, space, include } = params;
280+
281+
// constructing the relation type ids for the query
282+
const relationTypeIds = Utils.getRelationTypeIds(type, include);
283+
284+
const relationLevel = relationTypeIds.level2.length > 0 ? 2 : relationTypeIds.level1.length > 0 ? 1 : 0;
285+
286+
const queryDocument =
287+
relationLevel === 2
288+
? entityQueryDocumentLevel2
289+
: relationLevel === 1
290+
? entityQueryDocumentLevel1
291+
: entityQueryDocumentLevel0;
292+
293+
const result = await request<EntityQueryResult>(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, {
294+
id,
295+
spaceId: space,
296+
relationTypeIdsLevel1: relationTypeIds.level1,
297+
relationTypeIdsLevel2: relationTypeIds.level2,
298+
});
299+
300+
return parseResult(result, type);
301+
};

packages/hypergraph/src/entity/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './create.js';
22
export * from './delete.js';
33
export * from './find-many-private.js';
44
export * from './find-many-public.js';
5+
export * from './find-one-public.js';
56
export * from './findOne.js';
67
export * from './removeRelation.js';
78
export * from './schema.js';

0 commit comments

Comments
 (0)