Skip to content

Commit c0ef899

Browse files
authored
refactoring (#534)
1 parent 55b7821 commit c0ef899

22 files changed

+473
-527
lines changed

apps/connect/vitest.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import react from '@vitejs/plugin-react';
2+
import { mergeConfig } from 'vitest/config';
3+
4+
import shared from '../../vitest.shared.js';
5+
6+
const config = {
7+
plugins: [react()],
8+
test: {
9+
environment: 'jsdom',
10+
},
11+
};
12+
13+
export default mergeConfig(shared, config);

apps/events/vitest.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import react from '@vitejs/plugin-react';
2+
import { mergeConfig } from 'vitest/config';
3+
4+
import shared from '../../vitest.shared.js';
5+
6+
const config = {
7+
plugins: [react()],
8+
test: {
9+
environment: 'jsdom',
10+
},
11+
};
12+
13+
export default mergeConfig(shared, config);
Lines changed: 1 addition & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -1,271 +1,12 @@
11
'use client';
22

3-
import { Entity, type Id, store } from '@graphprotocol/hypergraph';
4-
import { useSelector } from '@xstate/store/react';
5-
import * as Schema from 'effect/Schema';
6-
import {
7-
createContext,
8-
type ReactNode,
9-
useContext,
10-
useEffect,
11-
useLayoutEffect,
12-
useMemo,
13-
useRef,
14-
useSyncExternalStore,
15-
} from 'react';
16-
import { useHypergraphApp } from './HypergraphAppContext.js';
17-
import { useEntityPublic } from './internal/use-entity-public.js';
18-
import { usePublicSpace } from './internal/use-public-space.js';
3+
import { createContext, type ReactNode } from 'react';
194

205
// TODO space can be undefined
216
export type HypergraphContext = { space: string };
227

238
export const HypergraphReactContext = createContext<HypergraphContext | undefined>(undefined);
249

25-
export function useHypergraphSpaceInternal() {
26-
const context = useContext(HypergraphReactContext);
27-
return (context as HypergraphContext) || { space: '' };
28-
}
29-
3010
export function HypergraphSpaceProvider({ space, children }: { space: string; children: ReactNode }) {
3111
return <HypergraphReactContext.Provider value={{ space }}>{children}</HypergraphReactContext.Provider>;
3212
}
33-
34-
const subscribeToSpaceCache = new Map<string, boolean>();
35-
36-
function useSubscribeToSpaceAndGetHandle({ spaceId, enabled }: { spaceId: string; enabled: boolean }) {
37-
const handle = useSelector(store, (state) => {
38-
const space = state.context.spaces.find((space) => space.id === spaceId);
39-
if (!space) {
40-
return undefined;
41-
}
42-
return space.automergeDocHandle;
43-
});
44-
45-
const { subscribeToSpace, isConnecting } = useHypergraphApp();
46-
useEffect(() => {
47-
if (!isConnecting && enabled) {
48-
if (subscribeToSpaceCache.has(spaceId)) {
49-
return;
50-
}
51-
subscribeToSpaceCache.set(spaceId, true);
52-
subscribeToSpace({ spaceId });
53-
}
54-
return () => {
55-
// TODO: unsubscribe from space in case the space ID changes
56-
subscribeToSpaceCache.delete(spaceId);
57-
};
58-
}, [isConnecting, subscribeToSpace, spaceId, enabled]);
59-
60-
return handle;
61-
}
62-
63-
export function useSpace(options: { space?: string; mode: 'private' | 'public' }) {
64-
const { space: spaceIdFromContext } = useHypergraphSpaceInternal();
65-
const { space: spaceIdFromParams } = options ?? {};
66-
const spaceId = spaceIdFromParams ?? spaceIdFromContext;
67-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId, enabled: options.mode === 'private' });
68-
const ready = options.mode === 'public' ? true : handle ? handle.isReady() : false;
69-
const privateSpace = useSelector(store, (state) => state.context.spaces.find((space) => space.id === spaceId));
70-
const publicSpace = usePublicSpace({ spaceId, enabled: options.mode === 'public' });
71-
return { ready, name: options.mode === 'private' ? privateSpace?.name : publicSpace?.name, id: spaceId };
72-
}
73-
74-
export function useCreateEntity<const S extends Entity.AnyNoContext>(type: S, options?: { space?: string }) {
75-
const { space: spaceIdFromParams } = options ?? {};
76-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
77-
const spaceId = spaceIdFromParams ?? spaceFromContext;
78-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId, enabled: true });
79-
if (!handle) {
80-
return () => {
81-
throw new Error('Space not found or not ready');
82-
};
83-
}
84-
return Entity.create(handle, type);
85-
}
86-
87-
export function useUpdateEntity<const S extends Entity.AnyNoContext>(type: S, options?: { space?: string }) {
88-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
89-
const { space } = options ?? {};
90-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
91-
if (!handle) {
92-
return () => {
93-
throw new Error('Space not found or not ready');
94-
};
95-
}
96-
return Entity.update(handle, type);
97-
}
98-
99-
export function useDeleteEntity(options?: { space?: string }) {
100-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
101-
const { space } = options ?? {};
102-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
103-
if (!handle) {
104-
return () => {
105-
throw new Error('Space not found or not ready');
106-
};
107-
}
108-
return Entity.markAsDeleted(handle);
109-
}
110-
111-
export function useRemoveRelation(options?: { space?: string }) {
112-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
113-
const { space } = options ?? {};
114-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
115-
if (!handle) {
116-
return () => {
117-
throw new Error('Space not found or not ready');
118-
};
119-
}
120-
return Entity.removeRelation(handle);
121-
}
122-
123-
export function useHardDeleteEntity(options?: { space?: string }) {
124-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
125-
const { space } = options ?? {};
126-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
127-
if (!handle) {
128-
return () => {
129-
throw new Error('Space not found or not ready');
130-
};
131-
}
132-
return Entity.delete(handle);
133-
}
134-
135-
type QueryParams<S extends Entity.AnyNoContext> = {
136-
space?: string | undefined;
137-
enabled: boolean;
138-
filter?: Entity.EntityFilter<Schema.Schema.Type<S>> | undefined;
139-
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
140-
};
141-
142-
export function useQueryLocal<const S extends Entity.AnyNoContext>(type: S, params?: QueryParams<S>) {
143-
const { enabled = true, filter, include, space: spaceFromParams } = params ?? {};
144-
const entitiesRef = useRef<Entity.Entity<S>[]>([]);
145-
const subscriptionRef = useRef<Entity.FindManySubscription<S>>({
146-
subscribe: () => () => undefined,
147-
getEntities: () => entitiesRef.current,
148-
});
149-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
150-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: spaceFromParams ?? spaceFromContext, enabled });
151-
const handleIsReady = handle ? handle.isReady() : false;
152-
153-
// biome-ignore lint/correctness/useExhaustiveDependencies: allow to change filter and include
154-
useLayoutEffect(() => {
155-
if (enabled && handle && handleIsReady) {
156-
const subscription = Entity.subscribeToFindMany(handle, type, filter, include);
157-
subscriptionRef.current.subscribe = subscription.subscribe;
158-
subscriptionRef.current.getEntities = subscription.getEntities;
159-
}
160-
}, [enabled, handleIsReady, handle, type]);
161-
162-
// TODO: allow to change the enabled state
163-
const allEntities = useSyncExternalStore(
164-
subscriptionRef.current.subscribe,
165-
subscriptionRef.current.getEntities,
166-
() => entitiesRef.current,
167-
);
168-
169-
const { entities, deletedEntities } = useMemo(() => {
170-
const entities: Entity.Entity<S>[] = [];
171-
const deletedEntities: Entity.Entity<S>[] = [];
172-
for (const entity of allEntities) {
173-
if (entity.__deleted === true) {
174-
deletedEntities.push(entity);
175-
} else {
176-
entities.push(entity);
177-
}
178-
}
179-
return { entities, deletedEntities };
180-
}, [allEntities]);
181-
182-
return { entities, deletedEntities };
183-
}
184-
185-
function useEntityPrivate<const S extends Entity.AnyNoContext>(
186-
type: S,
187-
params: {
188-
id: string | Id;
189-
enabled?: boolean;
190-
space?: string;
191-
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
192-
},
193-
) {
194-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
195-
const { space: spaceFromParams, include, id, enabled = true } = params;
196-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: spaceFromParams ?? spaceFromContext, enabled });
197-
const prevEntityRef = useRef<{
198-
data: Entity.Entity<S> | undefined;
199-
invalidEntity: Record<string, string | boolean | number | Date> | undefined;
200-
isPending: boolean;
201-
isError: boolean;
202-
}>({ data: undefined, invalidEntity: undefined, isPending: false, isError: false });
203-
const equals = Schema.equivalence(type);
204-
205-
const subscribe = (callback: () => void) => {
206-
if (!handle || !enabled) {
207-
return () => {};
208-
}
209-
const handleChange = () => {
210-
callback();
211-
};
212-
213-
const handleDelete = () => {
214-
callback();
215-
};
216-
217-
handle.on('change', handleChange);
218-
handle.on('delete', handleDelete);
219-
220-
return () => {
221-
handle.off('change', handleChange);
222-
handle.off('delete', handleDelete);
223-
};
224-
};
225-
226-
return useSyncExternalStore(subscribe, () => {
227-
if (!handle || !enabled) {
228-
return prevEntityRef.current;
229-
}
230-
const doc = handle.doc();
231-
if (doc === undefined) {
232-
return prevEntityRef.current;
233-
}
234-
235-
const found = Entity.findOne(handle, type, include)(id);
236-
if (found === undefined && prevEntityRef.current.data !== undefined) {
237-
// entity was maybe deleted, delete from the ref
238-
prevEntityRef.current = { data: undefined, invalidEntity: undefined, isPending: false, isError: false };
239-
} else if (found !== undefined && prevEntityRef.current.data === undefined) {
240-
prevEntityRef.current = { data: found, invalidEntity: undefined, isPending: false, isError: false };
241-
} else if (
242-
found !== undefined &&
243-
prevEntityRef.current.data !== undefined &&
244-
!equals(found, prevEntityRef.current.data)
245-
) {
246-
// found and ref have a value, compare for equality, if they are not equal, update the ref and return
247-
prevEntityRef.current = { data: found, invalidEntity: undefined, isPending: false, isError: false };
248-
}
249-
250-
return prevEntityRef.current;
251-
});
252-
}
253-
254-
export function useEntity<const S extends Entity.AnyNoContext>(
255-
type: S,
256-
params: {
257-
id: string | Id;
258-
space?: string;
259-
mode: 'private' | 'public';
260-
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
261-
},
262-
) {
263-
const resultPublic = useEntityPublic(type, { ...params, enabled: params.mode === 'public' });
264-
const resultPrivate = useEntityPrivate(type, { ...params, enabled: params.mode === 'private' });
265-
266-
if (params.mode === 'public') {
267-
return resultPublic;
268-
}
269-
270-
return resultPrivate;
271-
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client';
2+
3+
import { Entity } from '@graphprotocol/hypergraph';
4+
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
5+
import { useSubscribeToSpaceAndGetHandle } from '../internal/use-subscribe-to-space.js';
6+
7+
export function useCreateEntity<const S extends Entity.AnyNoContext>(type: S, options?: { space?: string }) {
8+
const { space: spaceIdFromParams } = options ?? {};
9+
const { space: spaceFromContext } = useHypergraphSpaceInternal();
10+
const spaceId = spaceIdFromParams ?? spaceFromContext;
11+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId, enabled: true });
12+
if (!handle) {
13+
return () => {
14+
throw new Error('Space not found or not ready');
15+
};
16+
}
17+
return Entity.create(handle, type);
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client';
2+
3+
import { Entity } from '@graphprotocol/hypergraph';
4+
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
5+
import { useSubscribeToSpaceAndGetHandle } from '../internal/use-subscribe-to-space.js';
6+
7+
export function useDeleteEntity(options?: { space?: string }) {
8+
const { space: spaceFromContext } = useHypergraphSpaceInternal();
9+
const { space } = options ?? {};
10+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
11+
if (!handle) {
12+
return () => {
13+
throw new Error('Space not found or not ready');
14+
};
15+
}
16+
return Entity.markAsDeleted(handle);
17+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Entity, Id } from '@graphprotocol/hypergraph';
2+
import type * as Schema from 'effect/Schema';
3+
import { useEntityPrivate } from '../internal/use-entity-private.js';
4+
import { useEntityPublic } from '../internal/use-entity-public.js';
5+
6+
export function useEntity<const S extends Entity.AnyNoContext>(
7+
type: S,
8+
params: {
9+
id: string | Id;
10+
space?: string;
11+
mode: 'private' | 'public';
12+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
13+
},
14+
) {
15+
const resultPublic = useEntityPublic(type, { ...params, enabled: params.mode === 'public' });
16+
const resultPrivate = useEntityPrivate(type, { ...params, enabled: params.mode === 'private' });
17+
18+
if (params.mode === 'public') {
19+
return resultPublic;
20+
}
21+
22+
return resultPrivate;
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client';
2+
3+
import { Entity } from '@graphprotocol/hypergraph';
4+
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
5+
import { useSubscribeToSpaceAndGetHandle } from '../internal/use-subscribe-to-space.js';
6+
7+
export function useHardDeleteEntity(options?: { space?: string }) {
8+
const { space: spaceFromContext } = useHypergraphSpaceInternal();
9+
const { space } = options ?? {};
10+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: space ?? spaceFromContext, enabled: true });
11+
if (!handle) {
12+
return () => {
13+
throw new Error('Space not found or not ready');
14+
};
15+
}
16+
return Entity.delete(handle);
17+
}

packages/hypergraph-react/src/use-query.tsx renamed to packages/hypergraph-react/src/hooks/use-query.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Entity } from '@graphprotocol/hypergraph';
22
import type * as Schema from 'effect/Schema';
3-
import { useQueryLocal } from './HypergraphSpaceContext.js';
4-
import { useQueryPublic } from './internal/use-query-public.js';
3+
import { useQueryPrivate } from '../internal/use-query-private.js';
4+
import { useQueryPublic } from '../internal/use-query-public.js';
55

66
type QueryParams<S extends Entity.AnyNoContext> = {
77
mode: 'public' | 'private';
@@ -17,7 +17,7 @@ const preparePublishDummy = () => undefined;
1717
export function useQuery<const S extends Entity.AnyNoContext>(type: S, params: QueryParams<S>) {
1818
const { mode, filter, include, space, first } = params;
1919
const publicResult = useQueryPublic(type, { enabled: mode === 'public', filter, include, first, space });
20-
const localResult = useQueryLocal(type, { enabled: mode === 'private', filter, include, space });
20+
const localResult = useQueryPrivate(type, { enabled: mode === 'private', filter, include, space });
2121

2222
if (mode === 'public') {
2323
return {

0 commit comments

Comments
 (0)