Skip to content

Commit 26cbcfc

Browse files
baiirunnikgraf
andauthored
fix: pass dataType for orderBy entity queries (#596)
Co-authored-by: Nik Graf <nik@nikgraf.com>
1 parent 4194dba commit 26cbcfc

4 files changed

Lines changed: 165 additions & 2 deletions

File tree

.changeset/thirty-pumas-fix.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+
fix orderBy entity queries
7+

packages/hypergraph/src/entity/find-many-public.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type FindManyPublicParams<
3535
const buildEntitiesQuery = (
3636
relationInfoLevel1: RelationTypeIdInfo[],
3737
useOrderBy: boolean,
38+
includeOrderByDataType: boolean,
3839
spaceSelection: SpaceSelection,
3940
includeSpaceIds: boolean,
4041
) => {
@@ -50,6 +51,7 @@ const buildEntitiesQuery = (
5051
: undefined,
5152
'$typeIds: [UUID!]!',
5253
useOrderBy ? '$propertyId: UUID!' : undefined,
54+
useOrderBy && includeOrderByDataType ? '$dataType: String' : undefined,
5355
useOrderBy ? '$sortDirection: SortOrder!' : undefined,
5456
'$first: Int',
5557
'$filter: EntityFilter!',
@@ -68,7 +70,8 @@ const buildEntitiesQuery = (
6870
// entitiesOrderedByProperty doesn't support the native typeIds filter yet,
6971
// so we fall back to the relation-based filter for orderBy queries
7072
if (useOrderBy) {
71-
const orderByArgs = 'propertyId: $propertyId\n sortDirection: $sortDirection\n ';
73+
const orderByDataTypeArg = includeOrderByDataType ? 'dataType: $dataType\n ' : '';
74+
const orderByArgs = `propertyId: $propertyId\n ${orderByDataTypeArg}sortDirection: $sortDirection\n `;
7275
const entitySpaceFilter =
7376
spaceSelection.mode === 'single'
7477
? 'spaceIds: {in: [$spaceId]},'
@@ -282,6 +285,7 @@ export const findManyPublic = async <
282285
);
283286

284287
let orderByPropertyId: string | undefined;
288+
let orderByDataType: Utils.OrderByDataType | undefined;
285289
let sortDirection: GraphSortDirection | undefined;
286290

287291
if (orderBy) {
@@ -304,6 +308,8 @@ export const findManyPublic = async <
304308
throw new Error(`Property "${String(orderBy.property)}" is missing a propertyId annotation`);
305309
}
306310

311+
orderByDataType = Utils.getOrderByDataType(propertyType);
312+
307313
orderByPropertyId = propertyIdAnnotation.value;
308314
sortDirection = orderBy.direction === 'asc' ? 'ASC' : 'DESC';
309315
}
@@ -312,7 +318,13 @@ export const findManyPublic = async <
312318
const spaceSelection = normalizeSpaceSelection(space, spaces);
313319

314320
// Build the query dynamically with aliases for each relation type ID
315-
const queryDocument = buildEntitiesQuery(relationTypeIds, Boolean(orderBy), spaceSelection, includeSpaceIds);
321+
const queryDocument = buildEntitiesQuery(
322+
relationTypeIds,
323+
Boolean(orderBy),
324+
Boolean(orderByDataType),
325+
spaceSelection,
326+
includeSpaceIds,
327+
);
316328

317329
const filterParams = filter ? Utils.translateFilterToGraphql(filter, type) : {};
318330

@@ -331,6 +343,9 @@ export const findManyPublic = async <
331343

332344
if (orderByPropertyId && sortDirection) {
333345
queryVariables.propertyId = orderByPropertyId;
346+
if (orderByDataType) {
347+
queryVariables.dataType = orderByDataType;
348+
}
334349
queryVariables.sortDirection = sortDirection;
335350
}
336351

packages/hypergraph/src/utils/convert-property-value.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,39 @@ import { Constants } from '@graphprotocol/hypergraph';
22
import * as Option from 'effect/Option';
33
import * as SchemaAST from 'effect/SchemaAST';
44

5+
export type OrderByDataType = 'text' | 'boolean' | 'float' | 'datetime' | 'point' | 'schedule';
6+
7+
const ORDER_BY_DATA_TYPE_BY_PROPERTY_TYPE: Record<string, OrderByDataType | undefined> = {
8+
string: 'text',
9+
boolean: 'boolean',
10+
number: 'float',
11+
date: 'datetime',
12+
point: 'point',
13+
schedule: 'schedule',
14+
};
15+
16+
export const getOrderByDataType = (type: SchemaAST.AST): OrderByDataType | undefined => {
17+
const propertyType = SchemaAST.getAnnotation<string>(Constants.PropertyTypeSymbol)(type);
18+
if (Option.isSome(propertyType)) {
19+
const mappedType = ORDER_BY_DATA_TYPE_BY_PROPERTY_TYPE[propertyType.value];
20+
if (mappedType) {
21+
return mappedType;
22+
}
23+
}
24+
25+
if (SchemaAST.isStringKeyword(type)) {
26+
return 'text';
27+
}
28+
if (SchemaAST.isBooleanKeyword(type)) {
29+
return 'boolean';
30+
}
31+
if (SchemaAST.isNumberKeyword(type)) {
32+
return 'float';
33+
}
34+
35+
return undefined;
36+
};
37+
538
export const convertPropertyValue = (
639
property: {
740
propertyId: string;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Id } from '@geoprotocol/geo-sdk';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { findManyPublic } from '../../src/entity/find-many-public.js';
4+
import * as Entity from '../../src/entity/index.js';
5+
import * as Type from '../../src/type/type.js';
6+
import { getOrderByDataType } from '../../src/utils/convert-property-value.js';
7+
8+
const mockRequest = vi.hoisted(() => vi.fn());
9+
10+
vi.mock('graphql-request', () => ({
11+
request: mockRequest,
12+
}));
13+
14+
const TITLE_PROPERTY_ID = Id('79c1a9510074401087d07501ef9d7b3d');
15+
const SCORE_PROPERTY_ID = Id('0f0f62df02194f16983ad2ae5fc43ee5');
16+
const CHILDREN_RELATION_PROPERTY_ID = Id('ca7c7167250249c490b084c147f9b12b');
17+
const CHILD_NAME_PROPERTY_ID = Id('25584af039414ab986f7a603305b19bb');
18+
19+
const Child = Entity.Schema(
20+
{
21+
name: Type.String,
22+
},
23+
{
24+
types: [Id('3c2ae3aa4ec141e3bc4c1fe7a5e07bc1')],
25+
properties: {
26+
name: CHILD_NAME_PROPERTY_ID,
27+
},
28+
},
29+
);
30+
31+
const Parent = Entity.Schema(
32+
{
33+
title: Type.String,
34+
score: Type.Number,
35+
children: Type.Relation(Child),
36+
},
37+
{
38+
types: [Id('af571d8c06d44add8cfa4c6b50412254')],
39+
properties: {
40+
title: TITLE_PROPERTY_ID,
41+
score: SCORE_PROPERTY_ID,
42+
children: CHILDREN_RELATION_PROPERTY_ID,
43+
},
44+
},
45+
);
46+
47+
describe('findManyPublic orderBy', () => {
48+
beforeEach(() => {
49+
mockRequest.mockReset();
50+
mockRequest.mockResolvedValue({ entities: [] });
51+
});
52+
53+
it('passes inferred dataType for sortable fields', async () => {
54+
await findManyPublic(Parent, {
55+
space: 'space-1',
56+
orderBy: {
57+
property: 'score',
58+
direction: 'desc',
59+
},
60+
logInvalidResults: false,
61+
});
62+
63+
expect(mockRequest).toHaveBeenCalledTimes(1);
64+
const [, queryDocument, queryVariables] = mockRequest.mock.calls[0];
65+
66+
expect(queryDocument as string).toContain('$dataType: String');
67+
expect(queryDocument as string).toContain('dataType: $dataType');
68+
expect(queryVariables).toMatchObject({
69+
propertyId: SCORE_PROPERTY_ID,
70+
dataType: 'float',
71+
sortDirection: 'DESC',
72+
});
73+
});
74+
75+
it('omits dataType for unresolved orderBy field types', async () => {
76+
await findManyPublic(Parent, {
77+
space: 'space-1',
78+
orderBy: {
79+
property: 'children',
80+
direction: 'asc',
81+
},
82+
logInvalidResults: false,
83+
});
84+
85+
expect(mockRequest).toHaveBeenCalledTimes(1);
86+
const [, queryDocument, queryVariables] = mockRequest.mock.calls[0];
87+
88+
expect(queryDocument as string).not.toContain('$dataType: String');
89+
expect(queryDocument as string).not.toContain('dataType: $dataType');
90+
expect(queryVariables).toMatchObject({
91+
propertyId: CHILDREN_RELATION_PROPERTY_ID,
92+
sortDirection: 'ASC',
93+
});
94+
expect((queryVariables as Record<string, unknown>).dataType).toBeUndefined();
95+
});
96+
});
97+
98+
describe('getOrderByDataType', () => {
99+
it('maps schema builder outputs to GraphQL order dataType values', () => {
100+
expect(getOrderByDataType(Type.String('prop').ast)).toBe('text');
101+
expect(getOrderByDataType(Type.Number('prop').ast)).toBe('float');
102+
expect(getOrderByDataType(Type.Boolean('prop').ast)).toBe('boolean');
103+
expect(getOrderByDataType(Type.Date('prop').ast)).toBe('datetime');
104+
expect(getOrderByDataType(Type.Point('prop').ast)).toBe('point');
105+
expect(getOrderByDataType(Type.ScheduleString('prop').ast)).toBe('schedule');
106+
expect(getOrderByDataType(Type.Relation(Child)('prop').ast)).toBeUndefined();
107+
});
108+
});

0 commit comments

Comments
 (0)