Skip to content

Commit 3e433d4

Browse files
committed
handle relations in public query
1 parent 40def7a commit 3e433d4

File tree

6 files changed

+142
-109
lines changed

6 files changed

+142
-109
lines changed

apps/events/src/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ export const Project = EntitySchema(
111111
name: Type.String,
112112
description: Type.optional(Type.String),
113113
x: Type.optional(Type.String),
114-
// avatar: Type.Relation(Image),
114+
avatar: Type.Relation(Image),
115115
},
116116
{
117117
types: [Id('484a18c5-030a-499c-b0f2-ef588ff16d50')],
118118
properties: {
119119
name: Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
120120
description: Id('9b1f76ff-9711-404c-861e-59dc3fa7d037'),
121121
x: Id('0d625978-4b3c-4b57-a86f-de45c997c73c'),
122-
// avatar: Id('1155beff-fad5-49b7-a2e0-da4777b8792c'),
122+
avatar: Id('1155beff-fad5-49b7-a2e0-da4777b8792c'),
123123
},
124124
},
125125
);
Lines changed: 120 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import { PropertyIdSymbol, TypeIdsSymbol } from '@graphprotocol/hypergraph/constants';
2+
import { isRelation } from '@graphprotocol/hypergraph/utils/isRelation';
3+
import * as Option from 'effect/Option';
14
import type * as Schema from 'effect/Schema';
5+
import * as SchemaAST from 'effect/SchemaAST';
26
import { convertPropertyValue } from './convert-property-value.js';
37

48
// A recursive representation of the entity structure returned by the public GraphQL
@@ -25,74 +29,132 @@ type RecursiveQueryEntity = {
2529
export const convertRelations = <S extends Schema.Schema.AnyNoContext>(queryEntity: RecursiveQueryEntity, type: S) => {
2630
const rawEntity: Record<string, string | boolean | number | unknown[] | Date> = {};
2731

28-
for (const [key, relationId] of Object.entries(mappingEntry?.relations ?? {})) {
29-
const properties = (queryEntity.relationsList ?? []).filter((a) => a.typeId === relationId);
30-
if (properties.length === 0) {
31-
rawEntity[key] = [] as unknown[];
32-
continue;
33-
}
34-
35-
const field = type.fields[key];
36-
if (!field) {
37-
// @ts-expect-error TODO: properly access the type.name
38-
console.error(`Field ${key} not found in ${type.name}`);
39-
continue;
40-
}
41-
const relationTransformation = field.ast.rest?.[0];
42-
if (!relationTransformation) {
43-
console.error(`Relation transformation for ${key} not found`);
44-
continue;
45-
}
32+
const ast = type.ast as SchemaAST.TypeLiteral;
4633

47-
const identifierAnnotation = SchemaAST.getIdentifierAnnotation(relationTransformation.type.to);
48-
if (Option.isNone(identifierAnnotation)) {
49-
console.error(`Relation identifier for ${key} not found`);
50-
continue;
51-
}
34+
// console.log('queryEntity', queryEntity);
5235

53-
const relationTypeName = identifierAnnotation.value;
36+
for (const prop of ast.propertySignatures) {
37+
const result = SchemaAST.getAnnotation<string>(PropertyIdSymbol)(prop.type);
5438

55-
const relationMappingEntry = mapping[relationTypeName];
56-
if (!relationMappingEntry) {
57-
console.error(`Relation mapping entry for ${relationTypeName} not found`);
58-
continue;
59-
}
39+
if (isRelation(prop.type)) {
40+
rawEntity[String(prop.name)] = [];
6041

61-
const newRelationEntities = properties.map((propertyEntry) => {
62-
// @ts-expect-error TODO: properly access the type.name
63-
const type = field.value;
64-
65-
let rawEntity: Record<string, string | boolean | number | unknown[] | Date> = {
66-
id: propertyEntry.toEntity.id,
67-
name: propertyEntry.toEntity.name,
68-
// TODO: should be determined by the actual value
69-
__deleted: false,
70-
// TODO: should be determined by the actual value
71-
__version: '',
72-
};
73-
74-
// take the mappingEntry and assign the attributes to the rawEntity
75-
for (const [key, value] of Object.entries(relationMappingEntry?.properties ?? {})) {
76-
const property = propertyEntry.toEntity.valuesList?.find((a) => a.propertyId === value);
77-
if (property) {
78-
rawEntity[key] = convertPropertyValue(property, key, type);
79-
}
42+
if (!queryEntity.valuesList) {
43+
continue;
8044
}
8145

82-
rawEntity = {
83-
...rawEntity,
84-
...convertRelations(propertyEntry.toEntity, type, relationMappingEntry, mapping),
85-
};
46+
if (Option.isSome(result)) {
47+
const relationTransformation = prop.type.rest?.[0]?.type;
48+
const typeIds: string[] = SchemaAST.getAnnotation<string[]>(TypeIdsSymbol)(relationTransformation).pipe(
49+
Option.getOrElse(() => []),
50+
);
51+
if (typeIds.length === 0) {
52+
continue;
53+
}
8654

87-
return rawEntity;
88-
});
55+
const allRelationsWithTheCorrectPropertyTypeId = queryEntity.relationsList?.filter(
56+
(a) => a.typeId === result.value,
57+
);
58+
if (allRelationsWithTheCorrectPropertyTypeId) {
59+
for (const relationEntry of allRelationsWithTheCorrectPropertyTypeId) {
60+
const nestedRawEntity:
61+
| Record<string, string | boolean | number | unknown[] | Date>
62+
| { _relation: { id: string } } = {
63+
id: relationEntry.toEntity.id,
64+
_relation: {
65+
id: 'TODO: relation id',
66+
},
67+
};
8968

90-
if (rawEntity[key]) {
91-
rawEntity[key] = [...(rawEntity[key] as unknown[]), ...newRelationEntities];
92-
} else {
93-
rawEntity[key] = newRelationEntities;
69+
for (const nestedProp of relationTransformation.propertySignatures) {
70+
const nestedResult = SchemaAST.getAnnotation<string>(PropertyIdSymbol)(nestedProp.type);
71+
if (Option.isSome(nestedResult)) {
72+
const value = relationEntry.toEntity.valuesList?.find((a) => a.propertyId === nestedResult.value);
73+
if (!value) {
74+
continue;
75+
}
76+
const rawValue = convertPropertyValue(value, nestedProp.type);
77+
if (rawValue) {
78+
nestedRawEntity[String(nestedProp.name)] = rawValue;
79+
}
80+
}
81+
// TODO: in the end every entry should be validated using the Schema?!?
82+
rawEntity[String(prop.name)] = [...(rawEntity[String(prop.name)] as unknown[]), nestedRawEntity];
83+
}
84+
}
85+
}
86+
}
9487
}
9588
}
9689

90+
// for (const [key, relationId] of Object.entries(mappingEntry?.relations ?? {})) {
91+
// const properties = (queryEntity.relationsList ?? []).filter((a) => a.typeId === relationId);
92+
// if (properties.length === 0) {
93+
// rawEntity[key] = [] as unknown[];
94+
// continue;
95+
// }
96+
97+
// const field = type.fields[key];
98+
// if (!field) {
99+
// // @ts-expect-error TODO: properly access the type.name
100+
// console.error(`Field ${key} not found in ${type.name}`);
101+
// continue;
102+
// }
103+
// const relationTransformation = field.ast.rest?.[0];
104+
// if (!relationTransformation) {
105+
// console.error(`Relation transformation for ${key} not found`);
106+
// continue;
107+
// }
108+
109+
// const identifierAnnotation = SchemaAST.getIdentifierAnnotation(relationTransformation.type.to);
110+
// if (Option.isNone(identifierAnnotation)) {
111+
// console.error(`Relation identifier for ${key} not found`);
112+
// continue;
113+
// }
114+
115+
// const relationTypeName = identifierAnnotation.value;
116+
117+
// const relationMappingEntry = mapping[relationTypeName];
118+
// if (!relationMappingEntry) {
119+
// console.error(`Relation mapping entry for ${relationTypeName} not found`);
120+
// continue;
121+
// }
122+
123+
// const newRelationEntities = properties.map((propertyEntry) => {
124+
// // @ts-expect-error TODO: properly access the type.name
125+
// const type = field.value;
126+
127+
// let rawEntity: Record<string, string | boolean | number | unknown[] | Date> = {
128+
// id: propertyEntry.toEntity.id,
129+
// name: propertyEntry.toEntity.name,
130+
// // TODO: should be determined by the actual value
131+
// __deleted: false,
132+
// // TODO: should be determined by the actual value
133+
// __version: '',
134+
// };
135+
136+
// // take the mappingEntry and assign the attributes to the rawEntity
137+
// for (const [key, value] of Object.entries(relationMappingEntry?.properties ?? {})) {
138+
// const property = propertyEntry.toEntity.valuesList?.find((a) => a.propertyId === value);
139+
// if (property) {
140+
// rawEntity[key] = convertPropertyValue(property, type);
141+
// }
142+
// }
143+
144+
// rawEntity = {
145+
// ...rawEntity,
146+
// ...convertRelations(propertyEntry.toEntity, type, relationMappingEntry, mapping),
147+
// };
148+
149+
// return rawEntity;
150+
// });
151+
152+
// if (rawEntity[key]) {
153+
// rawEntity[key] = [...(rawEntity[key] as unknown[]), ...newRelationEntities];
154+
// } else {
155+
// rawEntity[key] = newRelationEntities;
156+
// }
157+
// }
158+
97159
return rawEntity;
98160
};

packages/hypergraph-react/src/internal/use-query-public.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as SchemaAST from 'effect/SchemaAST';
1010
import { gql, request } from 'graphql-request';
1111
import { useMemo } from 'react';
1212
import { convertPropertyValue } from './convert-property-value.js';
13+
import { convertRelations } from './convert-relations.js';
1314
import { translateFilterToGraphql } from './translate-filter-to-graphql.js';
1415
import type { QueryPublicParams } from './types.js';
1516
import { useHypergraphSpaceInternal } from './use-hypergraph-space-internal.js';
@@ -210,7 +211,7 @@ export const parseResult = <S extends Schema.Schema.AnyNoContext>(queryData: Ent
210211

211212
rawEntity = {
212213
...rawEntity,
213-
// ...convertRelations(queryEntity, type),
214+
...convertRelations(queryEntity, type),
214215
};
215216

216217
const decodeResult = decode({

packages/hypergraph/src/entity/entity.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function encodeToGrc20Json<T extends object, E>(schema: Schema.Schema<T,
5050
const out: Record<string, unknown> = {};
5151

5252
for (const prop of ast.propertySignatures) {
53+
// TODO: what about optional properties here? usually we use prop.type.types[0] but we don't here?
5354
const result = SchemaAST.getAnnotation<string>(PropertyIdSymbol)(prop.type);
5455
if (Option.isSome(result)) {
5556
out[result.value] = (value as any)[prop.name];

packages/hypergraph/src/type-utils/type-utils.ts

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,5 @@
11
import * as Type from '../type/type.js';
22

3-
// biome-ignore lint/suspicious/noExplicitAny: TODO
4-
export const isStringOrOptionalStringType = (type: any) => {
5-
if (type.ast && type.ast._tag === 'PropertySignatureDeclaration' && type.ast.isOptional) {
6-
return type.from === Type.String;
7-
}
8-
return type === Type.String;
9-
};
10-
11-
// biome-ignore lint/suspicious/noExplicitAny: TODO
12-
export const isNumberOrOptionalNumberType = (type: any) => {
13-
if (type.ast && type.ast._tag === 'PropertySignatureDeclaration' && type.ast.isOptional) {
14-
return type.from === Type.Number;
15-
}
16-
return type === Type.Number;
17-
};
18-
19-
// biome-ignore lint/suspicious/noExplicitAny: TODO
20-
export const isDateOrOptionalDateType = (type: any) => {
21-
if (type.ast && type.ast._tag === 'PropertySignatureDeclaration' && type.ast.isOptional) {
22-
return type.from === Type.Date;
23-
}
24-
return type === Type.Date;
25-
};
26-
27-
// biome-ignore lint/suspicious/noExplicitAny: TODO
28-
export const isBooleanOrOptionalBooleanType = (type: any) => {
29-
if (type.ast && type.ast._tag === 'PropertySignatureDeclaration' && type.ast.isOptional) {
30-
return type.from === Type.Boolean;
31-
}
32-
return type === Type.Boolean;
33-
};
34-
35-
// biome-ignore lint/suspicious/noExplicitAny: TODO
36-
export const isPointOrOptionalPointType = (type: any) => {
37-
if (type.ast && type.ast._tag === 'PropertySignatureDeclaration' && type.ast.isOptional) {
38-
return type.from === Type.Point;
39-
}
40-
return type === Type.Point;
41-
};
42-
43-
// biome-ignore lint/suspicious/noExplicitAny: TODO
44-
export const isOptional = (type: any) => {
45-
return type.ast && type.ast._tag === 'PropertySignatureDeclaration' && type.ast.isOptional;
46-
};
47-
483
export const isStringType = (type: any) => {
494
return type === Type.String;
505
};

packages/hypergraph/src/type/type.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
import * as Option from 'effect/Option';
12
import * as Schema from 'effect/Schema';
2-
import { PropertyIdSymbol, PropertyTypeSymbol, RelationSchemaSymbol, RelationSymbol } from '../constants.js';
3+
import * as SchemaAST from 'effect/SchemaAST';
4+
import {
5+
PropertyIdSymbol,
6+
PropertyTypeSymbol,
7+
RelationSchemaSymbol,
8+
RelationSymbol,
9+
TypeIdsSymbol,
10+
} from '../constants.js';
311

412
/**
513
* Creates a String schema with the specified GRC-20 property ID
@@ -45,9 +53,15 @@ export const Point = (propertyId: string) =>
4553
export const Relation =
4654
<S extends Schema.Schema.AnyNoContext>(schema: S) =>
4755
(propertyId: string) => {
48-
const schemaWithId = Schema.extend(
56+
const typeIds = SchemaAST.getAnnotation<string[]>(TypeIdsSymbol)(schema.ast as SchemaAST.TypeLiteral).pipe(
57+
Option.getOrElse(() => []),
58+
);
59+
60+
const schemaWithId = Schema.extend(schema)(
4961
Schema.Struct({ id: Schema.String, _relation: Schema.Struct({ id: Schema.String }) }),
50-
)(schema);
62+
// manually adding the type ids to the schema since they get lost when extending the schema
63+
).pipe(Schema.annotations({ [TypeIdsSymbol]: typeIds }));
64+
5165
return Schema.Array(schemaWithId).pipe(
5266
Schema.annotations({
5367
[PropertyIdSymbol]: propertyId,

0 commit comments

Comments
 (0)