Skip to content

Commit 64ae0c3

Browse files
committed
add Entity.searchManyPublic
1 parent 887dbc3 commit 64ae0c3

3 files changed

Lines changed: 294 additions & 0 deletions

File tree

.changeset/neat-tires-tie.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.searchManyPublic
7+

packages/hypergraph/src/entity/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export * from './find-many-public.js';
55
export * from './findOne.js';
66
export * from './removeRelation.js';
77
export * from './schema.js';
8+
export * from './search-many-public.js';
89
export * from './types.js';
910
export * from './update.js';
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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+
export type SearchManyPublicParams<S extends Schema.Schema.AnyNoContext> = {
10+
query: string;
11+
filter?: Entity.EntityFilter<Schema.Schema.Type<S>> | undefined;
12+
// TODO: for multi-level nesting it should only allow the allowed properties instead of Record<string, Record<string, never>>
13+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
14+
space: string | undefined;
15+
first?: number | undefined;
16+
};
17+
18+
const searchQueryDocumentLevel0 = gql`
19+
query search($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $first: Int, $filter: EntityFilter!) {
20+
search(
21+
query: $query
22+
filter: { and: [{
23+
typeIds: {in: $typeIds},
24+
spaceIds: {in: [$spaceId]},
25+
}, $filter]}
26+
spaceId: $spaceId
27+
first: $first
28+
offset: 0
29+
) {
30+
id
31+
name
32+
valuesList(filter: {spaceId: {is: $spaceId}}) {
33+
propertyId
34+
string
35+
boolean
36+
number
37+
time
38+
point
39+
}
40+
}
41+
}
42+
`;
43+
44+
const searchQueryDocumentLevel1 = gql`
45+
query search($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUID!]!, $first: Int, $filter: EntityFilter!) {
46+
search(
47+
query: $query
48+
filter: { and: [{
49+
typeIds: {in: $typeIds},
50+
spaceIds: {in: [$spaceId]},
51+
}, $filter]}
52+
spaceId: $spaceId
53+
first: $first
54+
offset: 0
55+
) {
56+
id
57+
name
58+
valuesList(filter: {spaceId: {is: $spaceId}}) {
59+
propertyId
60+
string
61+
boolean
62+
number
63+
time
64+
point
65+
}
66+
relationsList(
67+
filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}},
68+
) {
69+
id
70+
toEntity {
71+
id
72+
name
73+
valuesList(filter: {spaceId: {is: $spaceId}}) {
74+
propertyId
75+
string
76+
boolean
77+
number
78+
time
79+
point
80+
}
81+
}
82+
typeId
83+
}
84+
}
85+
}
86+
`;
87+
88+
const searchQueryDocumentLevel2 = gql`
89+
query search($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUID!]!, $relationTypeIdsLevel2: [UUID!]!, $first: Int, $filter: EntityFilter!) {
90+
search(
91+
query: $query
92+
filter: { and: [{
93+
typeIds: {in: $typeIds},
94+
spaceIds: {in: [$spaceId]},
95+
}, $filter]}
96+
spaceId: $spaceId
97+
first: $first
98+
offset: 0
99+
) {
100+
id
101+
name
102+
valuesList(filter: {spaceId: {is: $spaceId}}) {
103+
propertyId
104+
string
105+
boolean
106+
number
107+
time
108+
point
109+
}
110+
relationsList(
111+
filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}},
112+
) {
113+
id
114+
toEntity {
115+
id
116+
name
117+
valuesList(filter: {spaceId: {is: $spaceId}}) {
118+
propertyId
119+
string
120+
boolean
121+
number
122+
time
123+
point
124+
}
125+
relationsList(
126+
filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel2}},
127+
# filter: {spaceId: {is: $spaceId}, toEntity: {relations: {some: {typeId: {is: "8f151ba4-de20-4e3c-9cb4-99ddf96f48f1"}, toEntityId: {in: $relationTypeIdsLevel2}}}}}
128+
) {
129+
id
130+
toEntity {
131+
id
132+
name
133+
valuesList(filter: {spaceId: {is: $spaceId}}) {
134+
propertyId
135+
string
136+
boolean
137+
number
138+
time
139+
point
140+
}
141+
}
142+
typeId
143+
}
144+
}
145+
typeId
146+
}
147+
}
148+
}
149+
`;
150+
151+
type SearchQueryResult = {
152+
search: {
153+
id: string;
154+
name: string;
155+
valuesList: {
156+
propertyId: string;
157+
string: string;
158+
boolean: boolean;
159+
number: number;
160+
time: string;
161+
point: string;
162+
}[];
163+
relationsList: {
164+
id: string;
165+
toEntity: {
166+
id: string;
167+
name: string;
168+
valuesList: {
169+
propertyId: string;
170+
string: string;
171+
boolean: boolean;
172+
number: number;
173+
time: string;
174+
point: string;
175+
}[];
176+
relationsList: {
177+
id: string;
178+
toEntity: {
179+
id: string;
180+
name: string;
181+
valuesList: {
182+
propertyId: string;
183+
string: string;
184+
boolean: boolean;
185+
number: number;
186+
time: string;
187+
point: string;
188+
}[];
189+
};
190+
typeId: string;
191+
}[];
192+
};
193+
typeId: string;
194+
}[];
195+
}[];
196+
};
197+
198+
const parseResult = <S extends Schema.Schema.AnyNoContext>(queryData: SearchQueryResult, type: S) => {
199+
const schemaWithId = Utils.addIdSchemaField(type);
200+
const decode = Schema.decodeUnknownEither(schemaWithId);
201+
const data: Entity.Entity<S>[] = [];
202+
const invalidEntities: Record<string, unknown>[] = [];
203+
204+
for (const queryEntity of queryData.search) {
205+
let rawEntity: Record<string, string | boolean | number | unknown[] | Date> = {
206+
id: queryEntity.id,
207+
};
208+
209+
const ast = type.ast as SchemaAST.TypeLiteral;
210+
211+
for (const prop of ast.propertySignatures) {
212+
const propType =
213+
prop.isOptional && SchemaAST.isUnion(prop.type)
214+
? (prop.type.types.find((member) => !SchemaAST.isUndefinedKeyword(member)) ?? prop.type)
215+
: prop.type;
216+
217+
const result = SchemaAST.getAnnotation<string>(Constants.PropertyIdSymbol)(propType);
218+
219+
if (Option.isSome(result)) {
220+
const value = queryEntity.valuesList.find((a) => a.propertyId === result.value);
221+
if (value) {
222+
const rawValue = Utils.convertPropertyValue(value, propType);
223+
if (rawValue) {
224+
rawEntity[String(prop.name)] = rawValue;
225+
}
226+
}
227+
}
228+
}
229+
230+
// @ts-expect-error
231+
rawEntity = {
232+
...rawEntity,
233+
...Utils.convertRelations(queryEntity, ast),
234+
};
235+
236+
const decodeResult = decode({
237+
...rawEntity,
238+
__deleted: false,
239+
});
240+
241+
if (Either.isRight(decodeResult)) {
242+
// injecting the schema to the entity to be able to access it in the preparePublish function
243+
data.push({ ...decodeResult.right, __schema: type });
244+
} else {
245+
invalidEntities.push(rawEntity);
246+
}
247+
}
248+
return { data, invalidEntities };
249+
};
250+
251+
export const searchManyPublic = async <S extends Schema.Schema.AnyNoContext>(
252+
type: S,
253+
params?: SearchManyPublicParams<S>,
254+
) => {
255+
const { query, filter, include, space, first = 100 } = params ?? {};
256+
257+
// constructing the relation type ids for the query
258+
const relationTypeIds = Utils.getRelationTypeIds(type, include);
259+
260+
const typeIds = SchemaAST.getAnnotation<string[]>(Constants.TypeIdsSymbol)(type.ast as SchemaAST.TypeLiteral).pipe(
261+
Option.getOrElse(() => []),
262+
);
263+
264+
let queryDocument = searchQueryDocumentLevel0;
265+
if (relationTypeIds.level1.length > 0) {
266+
queryDocument = searchQueryDocumentLevel1;
267+
}
268+
if (relationTypeIds.level2.length > 0) {
269+
queryDocument = searchQueryDocumentLevel2;
270+
}
271+
272+
const filterParams = filter ? Utils.translateFilterToGraphql(filter, type) : {};
273+
274+
const result = await request<SearchQueryResult>(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, {
275+
spaceId: space,
276+
typeIds,
277+
query,
278+
relationTypeIdsLevel1: relationTypeIds.level1,
279+
relationTypeIdsLevel2: relationTypeIds.level2,
280+
first,
281+
filter: filterParams,
282+
});
283+
284+
const { data, invalidEntities } = parseResult(result, type);
285+
return { data, invalidEntities };
286+
};

0 commit comments

Comments
 (0)