Skip to content

Commit e4a7f22

Browse files
nikgrafCopilot
andauthored
Nik/include space ids (#571)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3f8c22d commit e4a7f22

16 files changed

Lines changed: 521 additions & 67 deletions

File tree

.changeset/tiny-dodos-help.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/hypergraph-react": patch
3+
"@graphprotocol/hypergraph": patch
4+
---
5+
6+
Added `includeSpaceIds` parameter to `findOnePublic`, `findManyPublic`, `searchManyPublic`, `useEntities` and `useEntity`
7+

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,14 @@ function RouteComponent() {
3737
valueSpaces: ['021265e2-d839-47c3-8d03-0ee3dfb29ffc', '95a4a1cc-bfcc-4038-b7a1-02c513d27700'],
3838
},
3939
},
40+
skillsTotalCount: true,
4041
},
42+
includeSpaceIds: true,
4143
});
4244
console.log({ person, personInvalidEntity, personInvalidRelationEntities });
45+
if (person) {
46+
console.log('person', person.skillsTotalCount);
47+
}
4348

4449
const {
4550
data: podcast,

docs/docs/query-public-data.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,76 @@ const { data: project } = useEntity(Project, {
9090
- `relationSpaces` controls which spaces are searched for the relation edges themselves (`relations`/`backlinks`). Pass an array to whitelist specific spaces, `'all'` to drop the filter entirely, or `[]` if you intentionally want the branch to match nothing.
9191
- `valueSpaces` applies the same override to the `valuesList` lookups for the related entities. This lets you fetch relation edges from one space while trusting the canonical values that live in another.
9292

93-
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.
93+
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.
94+
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 normalized (no `null` entries) `spaceIds: string[]` array. The flag defaults to `false` so responses stay small unless you opt in.
98+
99+
#### `useEntities`
100+
101+
```tsx
102+
const { data: projects } = useEntities(Project, {
103+
mode: 'public',
104+
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
105+
includeSpaceIds: true,
106+
});
107+
108+
projects.forEach((project) => {
109+
console.log(project.id, project.spaceIds); // => ["3f3235...", "95a4a1..."]
110+
});
111+
```
112+
113+
#### `useEntity`
114+
115+
```tsx
116+
const { data: project } = useEntity(Project, {
117+
id: '9f130661-8c3f-4db7-9bdc-3ce69631c5ef',
118+
mode: 'public',
119+
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
120+
includeSpaceIds: true,
121+
});
122+
123+
console.log(project?.spaceIds); // => ["3f3235..."]
124+
```
125+
126+
#### `Entity.findOnePublic`
127+
128+
```ts
129+
const project = await Entity.findOnePublic(Project, {
130+
id: '9f130661-8c3f-4db7-9bdc-3ce69631c5ef',
131+
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
132+
includeSpaceIds: true,
133+
});
134+
135+
console.log(project?.spaceIds); // => ["3f3235...", "95a4a1..."]
136+
```
137+
138+
#### `Entity.findManyPublic`
139+
140+
```ts
141+
const { data: rounds } = await Entity.findManyPublic(InvestmentRound, {
142+
spaces: ['3f32353d-3b27-4a13-b71a-746f06e1f7db', '95a4a1cc-bfcc-4038-b7a1-02c513d27700'],
143+
includeSpaceIds: true,
144+
});
145+
146+
rounds.map((round) => ({
147+
id: round.id,
148+
spaceIds: round.spaceIds,
149+
}));
150+
```
151+
152+
#### `Entity.searchManyPublic`
153+
154+
```ts
155+
const { data: searchMatches } = await Entity.searchManyPublic(Project, {
156+
query: 'hypergraph',
157+
space: '3f32353d-3b27-4a13-b71a-746f06e1f7db',
158+
includeSpaceIds: true,
159+
});
160+
161+
console.log(searchMatches[0]?.spaceIds); // => ["3f3235..."]
162+
```
94163

95164
### Querying from a specific space
96165

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

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import type { Entity } from '@graphprotocol/hypergraph';
22
import type * as Schema from 'effect/Schema';
33
import { useHypergraphApp } from '../HypergraphAppContext.js';
44
import { useEntitiesPrivate } from '../internal/use-entities-private.js';
5-
import { useEntitiesPublic } from '../internal/use-entities-public.js';
5+
import { type UseEntitiesPublicResult, useEntitiesPublic } from '../internal/use-entities-public.js';
66
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
77

88
type SpaceSelectionInputOptionInContext = {
99
space?: never;
1010
spaces?: never;
1111
};
1212

13-
type UseEntitiesParams<S extends Schema.Schema.AnyNoContext> = (
14-
| Entity.SpaceSelectionInput
15-
| SpaceSelectionInputOptionInContext
16-
) & {
17-
mode: 'public' | 'private';
13+
type UseEntitiesParams<
14+
S extends Schema.Schema.AnyNoContext,
15+
IncludeSpaceIds extends boolean | undefined = boolean | undefined,
16+
Mode extends 'public' | 'private' = 'public' | 'private',
17+
> = (Entity.SpaceSelectionInput | SpaceSelectionInputOptionInContext) & {
18+
mode: Mode;
1819
filter?: Entity.EntityFilter<Schema.Schema.Type<S>> | undefined;
1920
// TODO: restrict multi-level nesting to the actual relation keys
2021
include?: Entity.EntityInclude<S> | undefined;
@@ -27,10 +28,34 @@ type UseEntitiesParams<S extends Schema.Schema.AnyNoContext> = (
2728
}
2829
| undefined;
2930
backlinksTotalCountsTypeId1?: string | undefined;
31+
includeSpaceIds?: IncludeSpaceIds;
3032
logInvalidResults?: boolean;
3133
};
3234

33-
export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S, params: UseEntitiesParams<S>) {
35+
type UseEntitiesPublicReturn<
36+
S extends Schema.Schema.AnyNoContext,
37+
IncludeSpaceIds extends boolean | undefined,
38+
> = UseEntitiesPublicResult<S, IncludeSpaceIds> & { deleted: [] };
39+
40+
type UseEntitiesPrivateResult<S extends Schema.Schema.AnyNoContext> = Omit<
41+
UseEntitiesPublicResult<S, false>,
42+
'data'
43+
> & {
44+
data: (Entity.Entity<S> & { backlinksTotalCountsTypeId1?: number })[];
45+
deleted: Entity.Entity<S>[];
46+
};
47+
48+
type UseEntitiesResult<
49+
S extends Schema.Schema.AnyNoContext,
50+
IncludeSpaceIds extends boolean | undefined,
51+
Mode extends 'public' | 'private',
52+
> = Mode extends 'public' ? UseEntitiesPublicReturn<S, IncludeSpaceIds> : UseEntitiesPrivateResult<S>;
53+
54+
export function useEntities<
55+
const S extends Schema.Schema.AnyNoContext,
56+
IncludeSpaceIds extends boolean | undefined = false,
57+
Mode extends 'public' | 'private' = 'public',
58+
>(type: S, params: UseEntitiesParams<S, IncludeSpaceIds, Mode>): UseEntitiesResult<S, IncludeSpaceIds, Mode> {
3459
const {
3560
mode,
3661
filter,
@@ -41,21 +66,23 @@ export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S,
4166
offset,
4267
orderBy,
4368
backlinksTotalCountsTypeId1,
69+
includeSpaceIds,
4470
logInvalidResults: logInvalidResultsParam,
4571
} = params;
4672
const { space: spaceFromContext } = useHypergraphSpaceInternal();
4773
const { logInvalidResults: contextLogInvalidResults = true } = useHypergraphApp();
4874
const logInvalidResults = logInvalidResultsParam ?? contextLogInvalidResults ?? true;
4975
const resolvedSpace = space ?? spaceFromContext;
5076
const publicSpaceParams = spaces ? { spaces } : { space: resolvedSpace };
51-
const publicResult = useEntitiesPublic(type, {
77+
const publicResult = useEntitiesPublic<S, IncludeSpaceIds>(type, {
5278
enabled: mode === 'public',
5379
filter,
5480
include,
5581
first,
5682
offset,
5783
orderBy,
5884
backlinksTotalCountsTypeId1,
85+
...(includeSpaceIds !== undefined ? { includeSpaceIds } : {}),
5986
...publicSpaceParams,
6087
logInvalidResults,
6188
});
@@ -65,12 +92,12 @@ export function useEntities<const S extends Schema.Schema.AnyNoContext>(type: S,
6592
return {
6693
...publicResult,
6794
deleted: [],
68-
};
95+
} as UseEntitiesResult<S, IncludeSpaceIds, Mode>;
6996
}
7097

7198
return {
7299
...publicResult,
73100
data: localResult.entities as (Entity.Entity<S> & { backlinksTotalCountsTypeId1?: number })[],
74101
deleted: localResult.deletedEntities,
75-
};
102+
} as UseEntitiesResult<S, IncludeSpaceIds, Mode>;
76103
}

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

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,60 @@ import type { Entity, Id } from '@graphprotocol/hypergraph';
22
import type * as Schema from 'effect/Schema';
33
import { useHypergraphApp } from '../HypergraphAppContext.js';
44
import { useEntityPrivate } from '../internal/use-entity-private.js';
5-
import { useEntityPublic } from '../internal/use-entity-public.js';
5+
import { type UseEntityPublicResult, useEntityPublic } from '../internal/use-entity-public.js';
66

7-
export function useEntity<const S extends Schema.Schema.AnyNoContext>(
8-
type: S,
9-
params: {
10-
id: string | Id;
11-
space?: string;
12-
mode: 'private' | 'public';
13-
include?: Entity.EntityInclude<S> | undefined;
14-
logInvalidResults?: boolean;
15-
},
16-
) {
17-
const { mode, logInvalidResults: logInvalidResultsParam, ...restParams } = params;
7+
type UseEntityParams<
8+
S extends Schema.Schema.AnyNoContext,
9+
IncludeSpaceIds extends boolean | undefined = boolean | undefined,
10+
Mode extends 'private' | 'public' = 'private' | 'public',
11+
> = {
12+
id: string | Id;
13+
space?: string;
14+
mode: Mode;
15+
include?: Entity.EntityInclude<S> | undefined;
16+
includeSpaceIds?: IncludeSpaceIds;
17+
logInvalidResults?: boolean;
18+
};
19+
20+
type UseEntityPrivateResult<S extends Schema.Schema.AnyNoContext> = Omit<
21+
UseEntityPublicResult<S, false>,
22+
'data' | 'invalidEntity' | 'invalidRelationEntities'
23+
> & {
24+
data: Entity.Entity<S> | undefined;
25+
invalidEntity: Entity.InvalidEntity | undefined;
26+
invalidRelationEntities: [];
27+
};
28+
29+
type UseEntityResult<
30+
S extends Schema.Schema.AnyNoContext,
31+
IncludeSpaceIds extends boolean | undefined,
32+
Mode extends 'public' | 'private',
33+
> = Mode extends 'public' ? UseEntityPublicResult<S, IncludeSpaceIds> : UseEntityPrivateResult<S>;
34+
35+
export function useEntity<
36+
const S extends Schema.Schema.AnyNoContext,
37+
IncludeSpaceIds extends boolean | undefined = false,
38+
Mode extends 'public' | 'private' = 'public',
39+
>(type: S, params: UseEntityParams<S, IncludeSpaceIds, Mode>): UseEntityResult<S, IncludeSpaceIds, Mode> {
40+
const { mode, includeSpaceIds, logInvalidResults: logInvalidResultsParam, ...restParams } = params;
1841
const { logInvalidResults: contextLogInvalidResults = true } = useHypergraphApp();
1942
const logInvalidResults = logInvalidResultsParam ?? contextLogInvalidResults ?? true;
20-
const resultPublic = useEntityPublic(type, {
43+
const resultPublic = useEntityPublic<S, IncludeSpaceIds>(type, {
2144
...restParams,
45+
...(includeSpaceIds !== undefined ? { includeSpaceIds } : {}),
2246
logInvalidResults,
2347
enabled: mode === 'public',
2448
});
2549
const resultPrivate = useEntityPrivate(type, { ...restParams, enabled: mode === 'private' });
2650

2751
if (mode === 'public') {
28-
return resultPublic;
52+
return resultPublic as UseEntityResult<S, IncludeSpaceIds, Mode>;
2953
}
3054

3155
return {
3256
...resultPublic,
3357
data: resultPrivate.data,
3458
invalidEntity: resultPrivate.invalidEntity,
3559
invalidRelationEntities: [],
36-
};
60+
} as UseEntityResult<S, IncludeSpaceIds, Mode>;
3761
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { Entity } from '@graphprotocol/hypergraph';
22
import type * as Schema from 'effect/Schema';
33

4-
export type QueryPublicParams<S extends Schema.Schema.AnyNoContext> = {
4+
export type QueryPublicParams<
5+
S extends Schema.Schema.AnyNoContext,
6+
IncludeSpaceIds extends boolean | undefined = boolean | undefined,
7+
> = {
58
enabled?: boolean | undefined;
6-
} & Entity.FindManyPublicParams<S>;
9+
} & Entity.FindManyPublicParams<S, IncludeSpaceIds>;

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import { Constants, Entity } from '@graphprotocol/hypergraph';
2+
import type { UseQueryResult } from '@tanstack/react-query';
23
import { useQuery as useQueryTanstack } from '@tanstack/react-query';
34
import * as Option from 'effect/Option';
45
import type * as Schema from 'effect/Schema';
56
import * as SchemaAST from 'effect/SchemaAST';
67
import type { QueryPublicParams } from './types.js';
78
import { useHypergraphSpaceInternal } from './use-hypergraph-space-internal.js';
89

9-
export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S, params?: QueryPublicParams<S>) => {
10+
type FindManyPublicResult<S extends Schema.Schema.AnyNoContext, IncludeSpaceIds extends boolean | undefined> = Awaited<
11+
ReturnType<typeof Entity.findManyPublic<S, IncludeSpaceIds>>
12+
>;
13+
14+
export type UseEntitiesPublicResult<
15+
S extends Schema.Schema.AnyNoContext,
16+
IncludeSpaceIds extends boolean | undefined,
17+
> = Omit<UseQueryResult<FindManyPublicResult<S, IncludeSpaceIds>, Error>, 'data'> & {
18+
data: FindManyPublicResult<S, IncludeSpaceIds>['data'];
19+
invalidEntities: FindManyPublicResult<S, IncludeSpaceIds>['invalidEntities'];
20+
invalidRelationEntities: FindManyPublicResult<S, IncludeSpaceIds>['invalidRelationEntities'];
21+
};
22+
23+
type UseEntitiesPublicFn = <S extends Schema.Schema.AnyNoContext, IncludeSpaceIds extends boolean | undefined = false>(
24+
type: S,
25+
params?: QueryPublicParams<S, IncludeSpaceIds>,
26+
) => UseEntitiesPublicResult<S, IncludeSpaceIds>;
27+
28+
export const useEntitiesPublic: UseEntitiesPublicFn = (type, params) => {
1029
const {
1130
enabled = true,
1231
filter,
@@ -17,8 +36,10 @@ export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S,
1736
offset,
1837
orderBy,
1938
backlinksTotalCountsTypeId1,
39+
includeSpaceIds: includeSpaceIdsParam,
2040
logInvalidResults = true,
2141
} = params ?? {};
42+
const includeSpaceIds = includeSpaceIdsParam ?? false;
2243
const { space: spaceFromContext } = useHypergraphSpaceInternal();
2344
const space = spaceFromParams ?? spaceFromContext;
2445
const spaceSelectionKey = spaces ?? space;
@@ -37,6 +58,7 @@ export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S,
3758
offset,
3859
orderBy,
3960
backlinksTotalCountsTypeId1,
61+
includeSpaceIds,
4062
],
4163
queryFn: async () => {
4264
return Entity.findManyPublic(type, {
@@ -47,6 +69,7 @@ export const useEntitiesPublic = <S extends Schema.Schema.AnyNoContext>(type: S,
4769
offset,
4870
orderBy,
4971
backlinksTotalCountsTypeId1,
72+
...(includeSpaceIdsParam !== undefined ? { includeSpaceIds: includeSpaceIdsParam } : {}),
5073
logInvalidResults,
5174
});
5275
},

0 commit comments

Comments
 (0)