Skip to content

Commit 2519dbc

Browse files
authored
fix loading handle (#262)
1 parent 81dbd36 commit 2519dbc

File tree

4 files changed

+72
-28
lines changed

4 files changed

+72
-28
lines changed

apps/events/src/components/users.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { UserEntry } from './user-entry.js';
88
export const Users = () => {
99
const { data: users } = useQuery(User, { mode: 'private' });
1010
const { ready: spaceReady } = useSpace({ mode: 'private' });
11-
const createEntity = useCreateEntity(User, { space: '1c954768-7e14-4f0f-9396-0fe9dcd55fe8' });
11+
const createEntity = useCreateEntity(User);
1212
const [newUserName, setNewUserName] = useState('');
1313

1414
if (!spaceReady) {

packages/hypergraph-react/src/HypergraphAppContext.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ import {
3535
useRef,
3636
useState,
3737
} from 'react';
38-
import type { Address } from 'viem';
39-
import type { Hex } from 'viem';
38+
import type { Address, Hex } from 'viem';
4039

4140
const decodeResponseMessage = Schema.decodeUnknownEither(Messages.ResponseMessage);
4241

@@ -362,10 +361,10 @@ export function HypergraphAppProvider({
362361
});
363362
const authorIdentity = await Identity.getVerifiedIdentity(update.accountAddress, syncServerUri);
364363
if (authorIdentity.signaturePublicKey !== signer) {
365-
console.error(
366-
`Received invalid signature, recovered signer is ${signer},
367-
expected ${authorIdentity.signaturePublicKey}`,
368-
);
364+
// console.error(
365+
// `Received invalid signature, recovered signer is ${signer},
366+
// expected ${authorIdentity.signaturePublicKey}`,
367+
// );
369368
// TODO bring back signature verfication
370369
// return { valid: false, update: new Uint8Array([]) };
371370
}

packages/hypergraph-react/src/HypergraphSpaceContext.tsx

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
'use client';
22

3-
import type { AnyDocumentId } from '@automerge/automerge-repo';
4-
import { useRepo } from '@automerge/automerge-repo-react-hooks';
5-
import { Entity, Utils, store } from '@graphprotocol/hypergraph';
3+
import { Entity, store } from '@graphprotocol/hypergraph';
64
import { useSelector } from '@xstate/store/react';
75
import * as Schema from 'effect/Schema';
86
import {
@@ -35,12 +33,13 @@ export function HypergraphSpaceProvider({ space, children }: { space: string; ch
3533
}
3634

3735
function useSubscribeToSpaceAndGetHandle({ spaceId, enabled }: { spaceId: string; enabled: boolean }) {
38-
const repo = useRepo();
39-
const handle = useMemo(() => {
40-
const id = Utils.idToAutomergeId(spaceId) as AnyDocumentId;
41-
const result = repo.findWithProgress<Entity.DocumentContent>(id);
42-
return result.handle;
43-
}, [spaceId, repo]);
36+
const handle = useSelector(store, (state) => {
37+
const space = state.context.spaces.find((space) => space.id === spaceId);
38+
if (!space) {
39+
return undefined;
40+
}
41+
return space.automergeDocHandle;
42+
});
4443

4544
const { subscribeToSpace, isConnecting } = useHypergraphApp();
4645
useEffect(() => {
@@ -61,43 +60,69 @@ export function useSpace(options: { space?: string; mode: 'private' | 'public' }
6160
const { space: spaceIdFromParams } = options ?? {};
6261
const spaceId = spaceIdFromParams ?? spaceIdFromContext;
6362
const handle = useSubscribeToSpaceAndGetHandle({ spaceId, enabled: options.mode === 'private' });
64-
const ready = options.mode === 'public' ? true : handle.isReady();
63+
const ready = options.mode === 'public' ? true : handle ? handle.isReady() : false;
6564
const space = useSelector(store, (state) => state.context.spaces.find((space) => space.id === spaceId));
6665
return { ready, name: space?.name, id: spaceId };
6766
}
6867

6968
export function useCreateEntity<const S extends Entity.AnyNoContext>(type: S, options?: { space?: string }) {
70-
const { space } = options ?? {};
69+
const { space: spaceIdFromParams } = options ?? {};
7170
const { space: spaceFromContext } = useHypergraphSpaceInternal();
72-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
71+
const spaceId = spaceIdFromParams ?? spaceFromContext;
72+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId, enabled: true });
73+
if (!handle) {
74+
return () => {
75+
throw new Error('Space not found or not ready');
76+
};
77+
}
7378
return Entity.create(handle, type);
7479
}
7580

7681
export function useUpdateEntity<const S extends Entity.AnyNoContext>(type: S, options?: { space?: string }) {
7782
const { space: spaceFromContext } = useHypergraphSpaceInternal();
7883
const { space } = options ?? {};
7984
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
85+
if (!handle) {
86+
return () => {
87+
throw new Error('Space not found or not ready');
88+
};
89+
}
8090
return Entity.update(handle, type);
8191
}
8292

8393
export function useDeleteEntity(options?: { space?: string }) {
8494
const { space: spaceFromContext } = useHypergraphSpaceInternal();
8595
const { space } = options ?? {};
8696
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
97+
if (!handle) {
98+
return () => {
99+
throw new Error('Space not found or not ready');
100+
};
101+
}
87102
return Entity.markAsDeleted(handle);
88103
}
89104

90105
export function useRemoveRelation(options?: { space?: string }) {
91106
const { space: spaceFromContext } = useHypergraphSpaceInternal();
92107
const { space } = options ?? {};
93108
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
109+
if (!handle) {
110+
return () => {
111+
throw new Error('Space not found or not ready');
112+
};
113+
}
94114
return Entity.removeRelation(handle);
95115
}
96116

97117
export function useHardDeleteEntity(options?: { space?: string }) {
98118
const { space: spaceFromContext } = useHypergraphSpaceInternal();
99119
const { space } = options ?? {};
100120
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
121+
if (!handle) {
122+
return () => {
123+
throw new Error('Space not found or not ready');
124+
};
125+
}
101126
return Entity.delete(handle);
102127
}
103128

@@ -117,11 +142,11 @@ export function useQueryLocal<const S extends Entity.AnyNoContext>(type: S, para
117142
});
118143
const { space: spaceFromContext } = useHypergraphSpaceInternal();
119144
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: spaceFromParams ?? spaceFromContext, enabled: true });
120-
const handleIsReady = handle.isReady();
145+
const handleIsReady = handle ? handle.isReady() : false;
121146

122147
// biome-ignore lint/correctness/useExhaustiveDependencies: allow to change filter and include
123148
useLayoutEffect(() => {
124-
if (enabled && handleIsReady) {
149+
if (enabled && handle && handleIsReady) {
125150
const subscription = Entity.subscribeToFindMany(handle, type, filter, include);
126151
subscriptionRef.current.subscribe = subscription.subscribe;
127152
subscriptionRef.current.getEntities = subscription.getEntities;
@@ -163,6 +188,9 @@ export function useQueryEntity<const S extends Entity.AnyNoContext>(
163188
const equals = Schema.equivalence(type);
164189

165190
const subscribe = (callback: () => void) => {
191+
if (!handle) {
192+
return () => {};
193+
}
166194
const handleChange = () => {
167195
callback();
168196
};
@@ -181,6 +209,9 @@ export function useQueryEntity<const S extends Entity.AnyNoContext>(
181209
};
182210

183211
return useSyncExternalStore(subscribe, () => {
212+
if (!handle) {
213+
return prevEntityRef.current;
214+
}
184215
const doc = handle.doc();
185216
if (doc === undefined) {
186217
return prevEntityRef.current;

packages/hypergraph-react/test/HypergraphSpaceContext.test.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { type AnyDocumentId, Repo } from '@automerge/automerge-repo';
1+
import { Repo } from '@automerge/automerge-repo';
22
import { RepoContext } from '@automerge/automerge-repo-react-hooks';
3-
import { Entity, Type, Utils } from '@graphprotocol/hypergraph';
3+
import { Entity, Type, store } from '@graphprotocol/hypergraph';
44
import '@testing-library/jest-dom/vitest';
55
import { act, cleanup, renderHook, waitFor } from '@testing-library/react';
66
// biome-ignore lint/style/useImportType: <explanation>
@@ -45,10 +45,24 @@ describe('HypergraphSpaceContext', () => {
4545

4646
beforeEach(() => {
4747
repo = new Repo({});
48-
const result = repo.findWithProgress(Utils.idToAutomergeId(spaceId) as AnyDocumentId);
49-
const automergeDocHandle = result.handle;
50-
// set it to ready to interact with the document
51-
automergeDocHandle.doneLoading();
48+
store.send({ type: 'setRepo', repo });
49+
store.send({
50+
type: 'setSpace',
51+
spaceId,
52+
spaceState: {
53+
id: spaceId,
54+
members: {},
55+
invitations: {},
56+
removedMembers: {},
57+
inboxes: {},
58+
lastEventHash: '',
59+
},
60+
name: 'Test Space',
61+
updates: { updates: [], firstUpdateClock: 0, lastUpdateClock: 0 },
62+
events: [],
63+
inboxes: [],
64+
keys: [],
65+
});
5266

5367
wrapper = ({ children }: Readonly<{ children: React.ReactNode }>) => (
5468
<RepoContext.Provider value={repo}>
@@ -59,8 +73,8 @@ describe('HypergraphSpaceContext', () => {
5973

6074
describe('useCreateEntity', () => {
6175
it('should be able to create an entity through the useCreateEntity Hook', async () => {
62-
const { result: createEntityResult } = renderHook(() => useCreateEntity(Event), { wrapper });
6376
const { result: queryEntitiesResult, rerender } = renderHook(() => useQueryLocal(Event), { wrapper });
77+
const { result: createEntityResult } = renderHook(() => useCreateEntity(Event), { wrapper });
6478

6579
let createdEntity: Entity.Entity<typeof Event> | null = null;
6680

0 commit comments

Comments
 (0)