Skip to content

Commit 59c5673

Browse files
committed
allow to include space ids
1 parent 3f8c22d commit 59c5673

12 files changed

Lines changed: 121 additions & 20 deletions

File tree

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { Person, Podcast, Topic } from '@/schema';
12
import { useEntities, useEntity, usePublicSpaces } from '@graphprotocol/hypergraph-react';
23
import { createLazyFileRoute } from '@tanstack/react-router';
3-
import { Person, Podcast, Topic } from '@/schema';
44

55
export const Route = createLazyFileRoute('/podcasts')({
66
component: RouteComponent,
@@ -38,8 +38,12 @@ function RouteComponent() {
3838
},
3939
},
4040
},
41+
includeSpaceIds: true,
4142
});
4243
console.log({ person, personInvalidEntity, personInvalidRelationEntities });
44+
if (person) {
45+
console.log('person', person.spaceIds);
46+
}
4347

4448
const {
4549
data: podcast,

docs/docs/query-public-data.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,26 @@ const { data: project } = useEntity(Project, {
9292

9393
Each nested branch can have its own `_config` settings,so you can attach `_config` anywhere within the two supported include levels. Mix and match the settings per branch to stitch together data that spans multiple public spaces without issuing separate queries.
9494

95+
### Accessing space IDs
96+
97+
Public entities can live in multiple spaces. Set `includeSpaceIds: true` on `useEntities`, `useEntity`, `Entity.findOnePublic`, `Entity.findManyPublic`, or `Entity.searchManyPublic` whenever you want the response entities to include a `spaceIds: string[]` array. The IDs are normalized (null entries removed) and the flag defaults to `false` to keep the GraphQL responses as small as possible.
98+
99+
```tsx
100+
const { data: projects } = useEntities(Project, {
101+
mode: 'public',
102+
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
103+
includeSpaceIds: true,
104+
});
105+
```
106+
107+
```ts
108+
const project = await Entity.findOnePublic(Project, {
109+
id: '9f130661-8c3f-4db7-9bdc-3ce69631c5ef',
110+
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
111+
includeSpaceIds: true,
112+
});
113+
```
114+
95115
### Querying from a specific space
96116

97117
You can also query from a specific space by passing in the `space` parameter.

packages/hypergraph-react/src/hooks/use-entities.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type UseEntitiesParams<S extends Schema.Schema.AnyNoContext> = (
2727
}
2828
| undefined;
2929
backlinksTotalCountsTypeId1?: string | undefined;
30+
includeSpaceIds?: boolean | undefined;
3031
logInvalidResults?: boolean;
3132
};
3233

@@ -41,6 +42,7 @@ export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S,
4142
offset,
4243
orderBy,
4344
backlinksTotalCountsTypeId1,
45+
includeSpaceIds,
4446
logInvalidResults: logInvalidResultsParam,
4547
} = params;
4648
const { space: spaceFromContext } = useHypergraphSpaceInternal();
@@ -56,6 +58,7 @@ export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S,
5658
offset,
5759
orderBy,
5860
backlinksTotalCountsTypeId1,
61+
includeSpaceIds,
5962
...publicSpaceParams,
6063
logInvalidResults,
6164
});

packages/hypergraph-react/src/hooks/use-entity.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ export function useEntity<const S extends Schema.Schema.AnyNoContext>(
1111
space?: string;
1212
mode: 'private' | 'public';
1313
include?: Entity.EntityInclude<S> | undefined;
14+
includeSpaceIds?: boolean | undefined;
1415
logInvalidResults?: boolean;
1516
},
1617
) {
17-
const { mode, logInvalidResults: logInvalidResultsParam, ...restParams } = params;
18+
const { mode, includeSpaceIds, logInvalidResults: logInvalidResultsParam, ...restParams } = params;
1819
const { logInvalidResults: contextLogInvalidResults = true } = useHypergraphApp();
1920
const logInvalidResults = logInvalidResultsParam ?? contextLogInvalidResults ?? true;
2021
const resultPublic = useEntityPublic(type, {
2122
...restParams,
23+
includeSpaceIds,
2224
logInvalidResults,
2325
enabled: mode === 'public',
2426
});

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S,
1717
offset,
1818
orderBy,
1919
backlinksTotalCountsTypeId1,
20+
includeSpaceIds = false,
2021
logInvalidResults = true,
2122
} = params ?? {};
2223
const { space: spaceFromContext } = useHypergraphSpaceInternal();
@@ -37,6 +38,7 @@ export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S,
3738
offset,
3839
orderBy,
3940
backlinksTotalCountsTypeId1,
41+
includeSpaceIds,
4042
],
4143
queryFn: async () => {
4244
return Entity.findManyPublic(type, {
@@ -47,6 +49,7 @@ export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S,
4749
offset,
4850
orderBy,
4951
backlinksTotalCountsTypeId1,
52+
includeSpaceIds,
5053
logInvalidResults,
5154
});
5255
},

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@ type UseEntityPublicParams<S extends Schema.Schema.AnyNoContext> = {
1111
space?: string;
1212
// TODO: restrict multi-level nesting to the actual relation keys
1313
include?: Entity.EntityInclude<S> | undefined;
14+
includeSpaceIds?: boolean | undefined;
1415
logInvalidResults?: boolean;
1516
};
1617

1718
export const useEntityPublic = <S extends Schema.Schema.AnyNoContext>(type: S, params: UseEntityPublicParams<S>) => {
18-
const { id, enabled = true, space: spaceFromParams, include, logInvalidResults = true } = params;
19+
const {
20+
id,
21+
enabled = true,
22+
space: spaceFromParams,
23+
include,
24+
includeSpaceIds = false,
25+
logInvalidResults = true,
26+
} = params;
1927
const { space: spaceFromContext } = useHypergraphSpaceInternal();
2028
const space = spaceFromParams ?? spaceFromContext;
2129

@@ -24,12 +32,13 @@ export const useEntityPublic = <S extends Schema.Schema.AnyNoContext>(type: S, p
2432
);
2533

2634
const result = useQueryTanstack({
27-
queryKey: ['hypergraph-public-entity', id, typeIds, space, include],
35+
queryKey: ['hypergraph-public-entity', id, typeIds, space, include, includeSpaceIds],
2836
queryFn: async () => {
2937
return Entity.findOnePublic(type, {
3038
id,
3139
space,
3240
include,
41+
includeSpaceIds,
3342
logInvalidResults,
3443
});
3544
},

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type * as ParseResult from 'effect/ParseResult';
66
import * as Schema from 'effect/Schema';
77
import * as SchemaAST from 'effect/SchemaAST';
88
import { request } from 'graphql-request';
9+
import { normalizeSpaceIds } from './internal/normalize-space-ids.js';
910
import type { InvalidRelationEntity, RelationsListWithNodes } from '../utils/convert-relations.js';
1011
import type { RelationTypeIdInfo } from '../utils/get-relation-type-ids.js';
1112
import { buildRelationsSelection } from '../utils/relation-query-helpers.js';
@@ -26,15 +27,18 @@ export type FindManyPublicParams<S extends Schema.Schema.AnyNoContext> = SpaceSe
2627
}
2728
| undefined;
2829
backlinksTotalCountsTypeId1?: string | undefined;
30+
includeSpaceIds?: boolean | undefined;
2931
logInvalidResults?: boolean | undefined;
3032
};
3133

3234
const buildEntitiesQuery = (
3335
relationInfoLevel1: RelationTypeIdInfo[],
3436
useOrderBy: boolean,
3537
spaceSelection: SpaceSelection,
38+
includeSpaceIds: boolean,
3639
) => {
3740
const level1Relations = buildRelationsSelection(relationInfoLevel1, spaceSelection.mode);
41+
const spaceIdsSelection = includeSpaceIds ? '\n spaceIds' : '';
3842

3943
const queryName = useOrderBy ? 'entitiesOrderedByProperty' : 'entities';
4044
const variableDefinitions = [
@@ -86,7 +90,7 @@ query ${queryName}(${variableDefinitions}) {
8690
offset: $offset
8791
) {
8892
id
89-
name
93+
name${spaceIdsSelection}
9094
valuesList${valuesListFilter} {
9195
propertyId
9296
string
@@ -123,6 +127,7 @@ export type EntityQueryResult = {
123127
entities: ({
124128
id: string;
125129
name: string;
130+
spaceIds: readonly (string | null)[] | null;
126131
valuesList: ValuesList;
127132
backlinksTotalCountsTypeId1: {
128133
totalCount: number;
@@ -139,7 +144,9 @@ export const parseResult = <S extends Schema.Schema.AnyNoContext>(
139144
queryData: EntityQueryResult,
140145
type: S,
141146
relationInfoLevel1: RelationTypeIdInfo[],
147+
options?: { includeSpaceIds?: boolean },
142148
) => {
149+
const includeSpaceIds = options?.includeSpaceIds ?? false;
143150
const schemaWithId = Utils.addIdSchemaField(type);
144151
const decode = Schema.decodeUnknownEither(schemaWithId);
145152
const data: (Entity.Entity<S> & { backlinksTotalCountsTypeId1?: number })[] = [];
@@ -192,14 +199,20 @@ export const parseResult = <S extends Schema.Schema.AnyNoContext>(
192199
});
193200

194201
if (Either.isRight(decodeResult)) {
202+
const normalizedSpaceIds = includeSpaceIds ? normalizeSpaceIds(queryEntity.spaceIds) : undefined;
195203
// injecting the schema to the entity to be able to access it in the preparePublish function
196204
data.push({
197205
...decodeResult.right,
206+
...(includeSpaceIds ? { spaceIds: normalizedSpaceIds } : {}),
198207
__schema: type,
199208
backlinksTotalCountsTypeId1: queryEntity.backlinksTotalCountsTypeId1?.totalCount,
200209
});
201210
} else {
202-
invalidEntities.push({ raw: rawEntity, error: decodeResult.left });
211+
const normalizedSpaceIds = includeSpaceIds ? normalizeSpaceIds(queryEntity.spaceIds) : undefined;
212+
invalidEntities.push({
213+
raw: includeSpaceIds ? { ...rawEntity, spaceIds: normalizedSpaceIds } : rawEntity,
214+
error: decodeResult.left,
215+
});
203216
}
204217
}
205218
return { data, invalidEntities, invalidRelationEntities };
@@ -218,6 +231,7 @@ export const findManyPublic = async <S extends Schema.Schema.AnyNoContext>(
218231
offset = 0,
219232
orderBy,
220233
backlinksTotalCountsTypeId1,
234+
includeSpaceIds = false,
221235
logInvalidResults = true,
222236
} = params ?? {};
223237

@@ -259,7 +273,7 @@ export const findManyPublic = async <S extends Schema.Schema.AnyNoContext>(
259273
const spaceSelection = normalizeSpaceSelection(space, spaces);
260274

261275
// Build the query dynamically with aliases for each relation type ID
262-
const queryDocument = buildEntitiesQuery(relationTypeIds, Boolean(orderBy), spaceSelection);
276+
const queryDocument = buildEntitiesQuery(relationTypeIds, Boolean(orderBy), spaceSelection, includeSpaceIds);
263277

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

@@ -290,7 +304,9 @@ export const findManyPublic = async <S extends Schema.Schema.AnyNoContext>(
290304

291305
const result = await request<EntityQueryResult>(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, queryVariables);
292306

293-
const { data, invalidEntities, invalidRelationEntities } = parseResult(result, type, relationTypeIds);
307+
const { data, invalidEntities, invalidRelationEntities } = parseResult(result, type, relationTypeIds, {
308+
includeSpaceIds,
309+
});
294310
if (logInvalidResults) {
295311
if (invalidEntities.length > 0) {
296312
console.warn('Entities where decoding failed were dropped', invalidEntities);

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { request } from 'graphql-request';
88
import type { RelationTypeIdInfo } from '../utils/get-relation-type-ids.js';
99
import { buildRelationsSelection } from '../utils/relation-query-helpers.js';
1010
import type { EntityQueryResult as MultiEntityQueryResult } from './find-many-public.js';
11+
import { normalizeSpaceIds } from './internal/normalize-space-ids.js';
1112

1213
type EntityQueryResult = {
1314
entity: MultiEntityQueryResult['entities'][number] | null;
@@ -18,19 +19,21 @@ export type FindOnePublicParams<S extends Schema.Schema.AnyNoContext> = {
1819
space: string;
1920
// TODO: restrict multi-level nesting to the actual relation keys
2021
include?: Entity.EntityInclude<S> | undefined;
22+
includeSpaceIds?: boolean | undefined;
2123
logInvalidResults?: boolean | undefined;
2224
};
2325

24-
const buildEntityQuery = (relationInfoLevel1: RelationTypeIdInfo[]) => {
26+
const buildEntityQuery = (relationInfoLevel1: RelationTypeIdInfo[], includeSpaceIds: boolean) => {
2527
const relationsSelection = buildRelationsSelection(relationInfoLevel1, 'single');
2628
const relationsSelectionBlock = relationsSelection ? `\n ${relationsSelection}\n` : '';
29+
const spaceIdsSelection = includeSpaceIds ? '\n spaceIds' : '';
2730
return `
2831
query entity($id: UUID!, $spaceId: UUID!) {
2932
entity(
3033
id: $id,
3134
) {
3235
id
33-
name
36+
name${spaceIdsSelection}
3437
valuesList(filter: {spaceId: {is: $spaceId}}) {
3538
propertyId
3639
string
@@ -48,7 +51,9 @@ const parseResult = <S extends Schema.Schema.AnyNoContext>(
4851
queryData: EntityQueryResult,
4952
type: S,
5053
relationInfoLevel1: RelationTypeIdInfo[],
54+
options?: { includeSpaceIds?: boolean },
5155
) => {
56+
const includeSpaceIds = options?.includeSpaceIds ?? false;
5257
if (!queryData.entity) {
5358
return {
5459
entity: null,
@@ -101,34 +106,43 @@ const parseResult = <S extends Schema.Schema.AnyNoContext>(
101106
});
102107

103108
if (Either.isRight(decodeResult)) {
109+
const normalizedSpaceIds = includeSpaceIds ? normalizeSpaceIds(queryEntity.spaceIds) : undefined;
110+
const enrichedEntity = {
111+
...decodeResult.right,
112+
...(includeSpaceIds ? { spaceIds: normalizedSpaceIds } : {}),
113+
} as Entity.Entity<S>;
104114
return {
105-
entity: { ...decodeResult.right } as Entity.Entity<S>,
115+
entity: enrichedEntity,
106116
invalidEntity: null,
107117
invalidRelationEntities: invalidRelations,
108118
};
109119
}
110120

121+
const normalizedSpaceIds = includeSpaceIds ? normalizeSpaceIds(queryEntity.spaceIds) : undefined;
111122
return {
112123
entity: null,
113-
invalidEntity: { raw: rawEntity, error: decodeResult.left },
124+
invalidEntity: {
125+
raw: includeSpaceIds ? { ...rawEntity, spaceIds: normalizedSpaceIds } : rawEntity,
126+
error: decodeResult.left,
127+
},
114128
invalidRelationEntities: invalidRelations,
115129
};
116130
};
117131

118132
export const findOnePublic = async <S extends Schema.Schema.AnyNoContext>(type: S, params: FindOnePublicParams<S>) => {
119-
const { id, space, include, logInvalidResults = true } = params;
133+
const { id, space, include, includeSpaceIds = false, logInvalidResults = true } = params;
120134

121135
// constructing the relation type ids for the query
122136
const relationTypeIds = Utils.getRelationTypeIds(type, include);
123137

124-
const queryDocument = buildEntityQuery(relationTypeIds);
138+
const queryDocument = buildEntityQuery(relationTypeIds, includeSpaceIds);
125139

126140
const result = await request<EntityQueryResult>(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, {
127141
id,
128142
spaceId: space,
129143
});
130144

131-
const parsed = parseResult(result, type, relationTypeIds);
145+
const parsed = parseResult(result, type, relationTypeIds, { includeSpaceIds });
132146
if (logInvalidResults) {
133147
if (parsed.invalidEntity) {
134148
console.warn('Entity decoding failed', parsed.invalidEntity);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const normalizeSpaceIds = (spaceIds?: readonly (string | null)[] | null) =>
2+
spaceIds?.filter((spaceId): spaceId is string => Boolean(spaceId)) ?? [];
3+

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ export type SearchManyPublicParams<S extends Schema.Schema.AnyNoContext> = {
1616
space: string | undefined;
1717
first?: number | undefined;
1818
offset?: number | undefined;
19+
includeSpaceIds?: boolean | undefined;
1920
};
2021

21-
const buildSearchQuery = (relationInfoLevel1: RelationTypeIdInfo[]) => {
22+
const buildSearchQuery = (relationInfoLevel1: RelationTypeIdInfo[], includeSpaceIds: boolean) => {
2223
const relationsSelection = buildRelationsSelection(relationInfoLevel1, 'single');
24+
const spaceIdsSelection = includeSpaceIds ? '\n spaceIds' : '';
2325

2426
return `
2527
query searchEntities($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $first: Int, $filter: EntityFilter!, $offset: Int) {
@@ -34,7 +36,7 @@ query searchEntities($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $firs
3436
offset: $offset
3537
) {
3638
id
37-
name
39+
name${spaceIdsSelection}
3840
valuesList(filter: {spaceId: {is: $spaceId}}) {
3941
propertyId
4042
string
@@ -52,7 +54,7 @@ export const searchManyPublic = async <S extends Schema.Schema.AnyNoContext>(
5254
type: S,
5355
params?: SearchManyPublicParams<S>,
5456
) => {
55-
const { query, filter, include, space, first = 100, offset = 0 } = params ?? {};
57+
const { query, filter, include, space, first = 100, offset = 0, includeSpaceIds = false } = params ?? {};
5658

5759
// constructing the relation type ids for the query
5860
const relationTypeIds = Utils.getRelationTypeIds(type, include);
@@ -61,7 +63,7 @@ export const searchManyPublic = async <S extends Schema.Schema.AnyNoContext>(
6163
Option.getOrElse(() => []),
6264
);
6365

64-
const queryDocument = buildSearchQuery(relationTypeIds);
66+
const queryDocument = buildSearchQuery(relationTypeIds, includeSpaceIds);
6567

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

@@ -74,6 +76,8 @@ export const searchManyPublic = async <S extends Schema.Schema.AnyNoContext>(
7476
offset,
7577
});
7678

77-
const { data, invalidEntities, invalidRelationEntities } = parseResult(result, type, relationTypeIds);
79+
const { data, invalidEntities, invalidRelationEntities } = parseResult(result, type, relationTypeIds, {
80+
includeSpaceIds,
81+
});
7882
return { data, invalidEntities, invalidRelationEntities };
7983
};

0 commit comments

Comments
 (0)