diff --git a/.changeset/calm-games-trade.md b/.changeset/calm-games-trade.md
new file mode 100644
index 00000000..fbfacf5f
--- /dev/null
+++ b/.changeset/calm-games-trade.md
@@ -0,0 +1,7 @@
+---
+"@graphprotocol/hypergraph-react": patch
+"@graphprotocol/hypergraph": patch
+---
+
+add orderBy to Entities.findManyPublic and useEntities(mode: 'public')
+
\ No newline at end of file
diff --git a/apps/events/src/routes/podcasts.lazy.tsx b/apps/events/src/routes/podcasts.lazy.tsx
index e5556256..05c8003c 100644
--- a/apps/events/src/routes/podcasts.lazy.tsx
+++ b/apps/events/src/routes/podcasts.lazy.tsx
@@ -16,6 +16,7 @@ function RouteComponent() {
include: {
projects: {},
},
+ orderBy: { property: 'dateFounded', direction: 'asc' },
});
console.log({ data, isLoading, isError });
return (
@@ -25,7 +26,9 @@ function RouteComponent() {
{isError &&
-
{podcast.name}
+
+ {podcast.dateFounded.toISOString()} {podcast.name}
+
{podcast.projects.map((project) => (
- {project.name}
diff --git a/packages/hypergraph-react/src/hooks/use-entities.tsx b/packages/hypergraph-react/src/hooks/use-entities.tsx
index 6932891c..d311e486 100644
--- a/packages/hypergraph-react/src/hooks/use-entities.tsx
+++ b/packages/hypergraph-react/src/hooks/use-entities.tsx
@@ -11,11 +11,25 @@ type UseEntitiesParams = {
space?: string | undefined;
first?: number | undefined;
offset?: number | undefined;
+ orderBy?:
+ | {
+ property: keyof Schema.Schema.Type;
+ direction: 'asc' | 'desc';
+ }
+ | undefined;
};
export function useEntities(type: S, params: UseEntitiesParams) {
- const { mode, filter, include, space, first, offset } = params;
- const publicResult = useEntitiesPublic(type, { enabled: mode === 'public', filter, include, first, offset, space });
+ const { mode, filter, include, space, first, offset, orderBy } = params;
+ const publicResult = useEntitiesPublic(type, {
+ enabled: mode === 'public',
+ filter,
+ include,
+ first,
+ offset,
+ space,
+ orderBy,
+ });
const localResult = useEntitiesPrivate(type, { enabled: mode === 'private', filter, include, space });
if (mode === 'public') {
diff --git a/packages/hypergraph-react/src/internal/types.ts b/packages/hypergraph-react/src/internal/types.ts
index 2c0f1c5d..259198b8 100644
--- a/packages/hypergraph-react/src/internal/types.ts
+++ b/packages/hypergraph-react/src/internal/types.ts
@@ -9,4 +9,10 @@ export type QueryPublicParams = {
space?: string | undefined;
first?: number | undefined;
offset?: number | undefined;
+ orderBy?:
+ | {
+ property: keyof Schema.Schema.Type;
+ direction: 'asc' | 'desc';
+ }
+ | undefined;
};
diff --git a/packages/hypergraph-react/src/internal/use-entities-public.tsx b/packages/hypergraph-react/src/internal/use-entities-public.tsx
index 2589aa1c..e8b9b7a1 100644
--- a/packages/hypergraph-react/src/internal/use-entities-public.tsx
+++ b/packages/hypergraph-react/src/internal/use-entities-public.tsx
@@ -7,7 +7,7 @@ import type { QueryPublicParams } from './types.js';
import { useHypergraphSpaceInternal } from './use-hypergraph-space-internal.js';
export const useEntitiesPublic = (type: S, params?: QueryPublicParams) => {
- const { enabled = true, filter, include, space: spaceFromParams, first = 100, offset } = params ?? {};
+ const { enabled = true, filter, include, space: spaceFromParams, first = 100, offset, orderBy } = params ?? {};
const { space: spaceFromContext } = useHypergraphSpaceInternal();
const space = spaceFromParams ?? spaceFromContext;
@@ -28,9 +28,10 @@ export const useEntitiesPublic = (type: S,
filter,
first,
offset,
+ orderBy,
],
queryFn: async () => {
- return Entity.findManyPublic(type, { filter, include, space, first, offset });
+ return Entity.findManyPublic(type, { filter, include, space, first, offset, orderBy });
},
enabled,
});
diff --git a/packages/hypergraph/src/entity/find-many-public.ts b/packages/hypergraph/src/entity/find-many-public.ts
index f7827f95..de8e01bc 100644
--- a/packages/hypergraph/src/entity/find-many-public.ts
+++ b/packages/hypergraph/src/entity/find-many-public.ts
@@ -13,6 +13,12 @@ export type FindManyPublicParams = {
space: string;
first?: number | undefined;
offset?: number | undefined;
+ orderBy?:
+ | {
+ property: keyof Schema.Schema.Type;
+ direction: 'asc' | 'desc';
+ }
+ | undefined;
};
const entitiesQueryDocumentLevel0 = gql`
@@ -172,6 +178,168 @@ query entities($spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUI
}
`;
+const entitiesOrderedByPropertyQueryDocumentLevel0 = gql`
+query entitiesOrderedByProperty($spaceId: UUID!, $typeIds: [UUID!]!, $first: Int, $filter: EntityFilter!, $offset: Int, $propertyId: UUID!, $sortDirection: SortOrder!) {
+ entities: entitiesOrderedByProperty(
+ filter: { and: [{
+ relations: {some: {typeId: {is: "8f151ba4-de20-4e3c-9cb4-99ddf96f48f1"}, toEntityId: {in: $typeIds}}},
+ spaceIds: {in: [$spaceId]},
+ }, $filter]}
+ first: $first
+ offset: $offset
+ propertyId: $propertyId
+ sortDirection: $sortDirection
+ ) {
+ id
+ name
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ }
+}
+`;
+
+const entitiesOrderedByPropertyQueryDocumentLevel1 = gql`
+query entitiesOrderedByProperty($spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUID!]!, $first: Int, $filter: EntityFilter!, $offset: Int, $propertyId: UUID!, $sortDirection: SortOrder!) {
+ entities: entitiesOrderedByProperty(
+ first: $first
+ filter: { and: [{
+ relations: {some: {typeId: {is: "8f151ba4-de20-4e3c-9cb4-99ddf96f48f1"}, toEntityId: {in: $typeIds}}},
+ spaceIds: {in: [$spaceId]},
+ }, $filter]}
+ offset: $offset
+ propertyId: $propertyId
+ sortDirection: $sortDirection
+ ) {
+ id
+ name
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ relationsList(
+ filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}},
+ ) {
+ id
+ entity {
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ }
+ toEntity {
+ id
+ name
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ }
+ typeId
+ }
+ }
+}
+`;
+
+const entitiesOrderedByPropertyQueryDocumentLevel2 = gql`
+query entitiesOrderedByProperty($spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUID!]!, $relationTypeIdsLevel2: [UUID!]!, $first: Int, $filter: EntityFilter!, $offset: Int, $propertyId: UUID!, $sortDirection: SortOrder!) {
+ entities: entitiesOrderedByProperty(
+ first: $first
+ filter: { and: [{
+ relations: {some: {typeId: {is: "8f151ba4-de20-4e3c-9cb4-99ddf96f48f1"}, toEntityId: {in: $typeIds}}},
+ spaceIds: {in: [$spaceId]},
+ }, $filter]}
+ offset: $offset
+ propertyId: $propertyId
+ sortDirection: $sortDirection
+ ) {
+ id
+ name
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ relationsList(
+ filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}},
+ ) {
+ id
+ entity {
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ }
+ toEntity {
+ id
+ name
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ relationsList(
+ filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel2}},
+ ) {
+ id
+ entity {
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ }
+ toEntity {
+ id
+ name
+ valuesList(filter: {spaceId: {is: $spaceId}}) {
+ propertyId
+ string
+ boolean
+ number
+ time
+ point
+ }
+ }
+ typeId
+ }
+ }
+ typeId
+ }
+ }
+}
+`;
+
type EntityQueryResult = {
entities: {
id: string;
@@ -239,6 +407,8 @@ type EntityQueryResult = {
}[];
};
+type GraphSortDirection = 'ASC' | 'DESC';
+
export const parseResult = (queryData: EntityQueryResult, type: S) => {
const schemaWithId = Utils.addIdSchemaField(type);
const decode = Schema.decodeUnknownEither(schemaWithId);
@@ -296,7 +466,7 @@ export const findManyPublic = async (
type: S,
params?: FindManyPublicParams,
) => {
- const { filter, include, space, first = 100, offset = 0 } = params ?? {};
+ const { filter, include, space, first = 100, offset = 0, orderBy } = params ?? {};
// constructing the relation type ids for the query
const relationTypeIds = Utils.getRelationTypeIds(type, include);
@@ -305,17 +475,51 @@ export const findManyPublic = async (
Option.getOrElse(() => []),
);
- let queryDocument = entitiesQueryDocumentLevel0;
- if (relationTypeIds.level1.length > 0) {
- queryDocument = entitiesQueryDocumentLevel1;
- }
- if (relationTypeIds.level2.length > 0) {
- queryDocument = entitiesQueryDocumentLevel2;
+ const relationLevel = relationTypeIds.level2.length > 0 ? 2 : relationTypeIds.level1.length > 0 ? 1 : 0;
+
+ let orderByPropertyId: string | undefined;
+ let sortDirection: GraphSortDirection | undefined;
+
+ if (orderBy) {
+ const ast = type.ast as SchemaAST.TypeLiteral;
+ const propertySignature = ast.propertySignatures.find((prop) => String(prop.name) === String(orderBy.property));
+
+ if (!propertySignature) {
+ throw new Error(`Cannot order by unknown property "${String(orderBy.property)}"`);
+ }
+
+ const propertyType =
+ propertySignature.isOptional && SchemaAST.isUnion(propertySignature.type)
+ ? (propertySignature.type.types.find((member) => !SchemaAST.isUndefinedKeyword(member)) ??
+ propertySignature.type)
+ : propertySignature.type;
+
+ const propertyIdAnnotation = SchemaAST.getAnnotation(Constants.PropertyIdSymbol)(propertyType);
+
+ if (Option.isNone(propertyIdAnnotation)) {
+ throw new Error(`Property "${String(orderBy.property)}" is missing a propertyId annotation`);
+ }
+
+ orderByPropertyId = propertyIdAnnotation.value;
+ sortDirection = orderBy.direction === 'asc' ? 'ASC' : 'DESC';
}
+ const queryDocument =
+ relationLevel === 2
+ ? orderBy
+ ? entitiesOrderedByPropertyQueryDocumentLevel2
+ : entitiesQueryDocumentLevel2
+ : relationLevel === 1
+ ? orderBy
+ ? entitiesOrderedByPropertyQueryDocumentLevel1
+ : entitiesQueryDocumentLevel1
+ : orderBy
+ ? entitiesOrderedByPropertyQueryDocumentLevel0
+ : entitiesQueryDocumentLevel0;
+
const filterParams = filter ? Utils.translateFilterToGraphql(filter, type) : {};
- const result = await request(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, {
+ const queryVariables: Record = {
spaceId: space,
typeIds,
relationTypeIdsLevel1: relationTypeIds.level1,
@@ -323,7 +527,14 @@ export const findManyPublic = async (
first,
filter: filterParams,
offset,
- });
+ };
+
+ if (orderByPropertyId && sortDirection) {
+ queryVariables.propertyId = orderByPropertyId;
+ queryVariables.sortDirection = sortDirection;
+ }
+
+ const result = await request(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, queryVariables);
const { data, invalidEntities } = parseResult(result, type);
return { data, invalidEntities };