Skip to content

Commit d8cc003

Browse files
committed
migrate to new schema
1 parent 4c6ed64 commit d8cc003

File tree

18 files changed

+913
-30
lines changed

18 files changed

+913
-30
lines changed

apps/events/src/components/todos.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
import {
2-
useCreateEntity,
2+
useCreateEntityNew,
33
useDeleteEntity,
4-
useQuery,
4+
useQueryNew,
55
useRemoveRelation,
66
useSpace,
77
useUpdateEntity,
88
} from '@graphprotocol/hypergraph-react';
99
import { useEffect, useState } from 'react';
1010
import Select from 'react-select';
11-
import { Todo, User } from '../schema';
11+
import { Todo, TodoNew, UserNew } from '../schema';
1212
import { Button } from './ui/button';
1313
import { Input } from './ui/input';
1414

1515
export const Todos = () => {
16-
const { data: todos } = useQuery(Todo, {
16+
const { data: todos } = useQueryNew(TodoNew, {
1717
mode: 'private',
18-
include: { assignees: {} },
18+
// include: { assignees: {} },
1919
// filter: { or: [{ name: { startsWith: 'aa' } }, { name: { is: 'sdasd' } }] },
2020
});
21-
const { data: users } = useQuery(User, { mode: 'private' });
21+
const { data: users } = useQueryNew(UserNew, { mode: 'private' });
2222
const { ready: spaceReady } = useSpace({ mode: 'private' });
23-
const createEntity = useCreateEntity(Todo);
23+
const createEntity = useCreateEntityNew(TodoNew);
2424
const updateEntity = useUpdateEntity(Todo);
2525
const deleteEntity = useDeleteEntity();
2626
const removeRelation = useRemoveRelation();
2727
const [newTodoName, setNewTodoName] = useState('');
2828
const [assignees, setAssignees] = useState<{ value: string; label: string }[]>([]);
2929

30+
console.log(todos);
31+
3032
useEffect(() => {
3133
setAssignees((prevFilteredAssignees) => {
3234
// filter out assignees that are not in the users array whenever users change
@@ -54,7 +56,7 @@ export const Todos = () => {
5456
createEntity({
5557
name: newTodoName,
5658
completed: false,
57-
assignees: assignees.map(({ value }) => value),
59+
// assignees: assignees.map(({ value }) => value),
5860
});
5961
setNewTodoName('');
6062
}}
@@ -65,7 +67,7 @@ export const Todos = () => {
6567
{todos.map((todo) => (
6668
<div key={todo.id} className="flex flex-row items-center gap-2">
6769
<h2>{todo.name}</h2>
68-
{todo.assignees.length > 0 && (
70+
{/* {todo.assignees.length > 0 && (
6971
<span className="text-xs text-gray-500">
7072
Assigned to:{' '}
7173
{todo.assignees.map((assignee) => (
@@ -85,7 +87,7 @@ export const Todos = () => {
8587
</span>
8688
))}
8789
</span>
88-
)}
90+
)} */}
8991
<input
9092
type="checkbox"
9193
checked={todo.completed}

apps/events/src/components/users.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import { useCreateEntity, useQuery, useSpace } from '@graphprotocol/hypergraph-react';
1+
import { useCreateEntityNew, useQueryNew, useSpace } from '@graphprotocol/hypergraph-react';
22
import { useState } from 'react';
3-
import { User } from '../schema.js';
3+
import { UserNew } from '../schema.js';
44
import { Button } from './ui/button.js';
55
import { Input } from './ui/input.js';
66
import { UserEntry } from './user-entry.js';
77

88
export const Users = () => {
9-
const { data: users } = useQuery(User, { mode: 'private' });
9+
const { data: users } = useQueryNew(UserNew, { mode: 'private' });
1010
const { ready: spaceReady } = useSpace({ mode: 'private' });
11-
const createEntity = useCreateEntity(User);
11+
const createEntityNew = useCreateEntityNew(UserNew);
1212
const [newUserName, setNewUserName] = useState('');
1313

14+
console.log(users);
15+
1416
if (!spaceReady) {
1517
return <div>Loading space...</div>;
1618
}
@@ -22,7 +24,9 @@ export const Users = () => {
2224
<Input type="text" value={newUserName} onChange={(e) => setNewUserName(e.target.value)} />
2325
<Button
2426
onClick={() => {
25-
createEntity({ name: newUserName });
27+
// createEntity({ name: newUserName });
28+
const user = createEntityNew({ name: newUserName });
29+
console.log(user);
2630
setNewUserName('');
2731
}}
2832
>

apps/events/src/schema.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Entity, Type } from '@graphprotocol/hypergraph';
1+
import { Entity, EntityNew, Id, Type, TypeNew } from '@graphprotocol/hypergraph';
22

33
export class User extends Entity.Class<User>('User')({
44
name: Type.String,
@@ -41,3 +41,44 @@ export class Todo3 extends Entity.Class<Todo3>('Todo3')({
4141
completed: Type.Boolean,
4242
description: Type.String,
4343
}) {}
44+
45+
export const UserNew = EntityNew(
46+
{
47+
name: TypeNew.String,
48+
},
49+
{
50+
types: [Id('bffa181e-a333-495b-949c-57f2831d7eca')],
51+
properties: {
52+
name: Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
53+
},
54+
},
55+
);
56+
57+
export const TodoNew = EntityNew(
58+
{
59+
name: TypeNew.String,
60+
completed: TypeNew.Boolean,
61+
// assignees: TypeNew.Relation(UserNew),
62+
},
63+
{
64+
types: [Id('44fe82a9-e4c2-4330-a395-ce85ed78e421')],
65+
properties: {
66+
name: Id('c668aa67-bbca-4b2c-908c-9c5599035eab'),
67+
completed: Id('71e7654f-2623-4794-88fb-841c8f3dd9b4'),
68+
},
69+
},
70+
);
71+
72+
export const JobOfferNew = EntityNew(
73+
{
74+
name: TypeNew.String,
75+
salary: TypeNew.Number,
76+
},
77+
{
78+
types: [Id('bffa181e-a333-495b-949c-57f2831d7eca')],
79+
properties: {
80+
name: Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
81+
salary: Id('baa36ac9-78ac-4cf7-8394-6b2d3006bebe'),
82+
},
83+
},
84+
);

packages/hypergraph-react/src/hooks/use-create-entity.ts

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

33
import { Entity } from '@graphprotocol/hypergraph';
4+
import type * as Schema from 'effect/Schema';
45
import { useHypergraphSpaceInternal } from '../internal/use-hypergraph-space-internal.js';
56
import { useSubscribeToSpaceAndGetHandle } from '../internal/use-subscribe-to-space.js';
67

@@ -16,3 +17,16 @@ export function useCreateEntity<const S extends Entity.AnyNoContext>(type: S, op
1617
}
1718
return Entity.create(handle, type);
1819
}
20+
21+
export function useCreateEntityNew<const S extends Schema.Schema.AnyNoContext>(type: S, options?: { space?: string }) {
22+
const { space: spaceIdFromParams } = options ?? {};
23+
const { space: spaceFromContext } = useHypergraphSpaceInternal();
24+
const spaceId = spaceIdFromParams ?? spaceFromContext;
25+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId, enabled: true });
26+
if (!handle) {
27+
return () => {
28+
throw new Error('Space not found or not ready');
29+
};
30+
}
31+
return Entity.createNew(handle, type);
32+
}

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

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

66
type QueryParams<S extends Entity.AnyNoContext> = {
@@ -34,3 +34,33 @@ export function useQuery<const S extends Entity.AnyNoContext>(type: S, params: Q
3434
preparePublish: preparePublishDummy,
3535
};
3636
}
37+
38+
type QueryParamsNew<S extends Schema.Schema.AnyNoContext> = {
39+
mode: 'public' | 'private';
40+
filter?: Entity.EntityFilter<Schema.Schema.Type<S>> | undefined;
41+
// TODO: for multi-level nesting it should only allow the allowed properties instead of Record<string, Record<string, never>>
42+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
43+
space?: string | undefined;
44+
first?: number | undefined;
45+
};
46+
47+
export function useQueryNew<const S extends Schema.Schema.AnyNoContext>(type: S, params: QueryParamsNew<S>) {
48+
const { mode, filter, include, space, first } = params;
49+
const publicResult = useQueryPublic(type, { enabled: mode === 'public', filter, include, first, space });
50+
const localResult = useQueryPrivateNew(type, { enabled: mode === 'private', filter, include, space });
51+
52+
if (mode === 'public') {
53+
return {
54+
...publicResult,
55+
deleted: [],
56+
preparePublish: preparePublishDummy,
57+
};
58+
}
59+
60+
return {
61+
...publicResult,
62+
data: localResult.entities,
63+
deleted: localResult.deletedEntities,
64+
preparePublish: preparePublishDummy,
65+
};
66+
}

packages/hypergraph-react/src/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
export { PublishDiff } from './components/publish-diff/publish-diff.js';
22
export { createWalletClient } from './create-wallet-client.js';
3-
export {
4-
HypergraphAppProvider,
5-
useHypergraphApp,
6-
useHypergraphAuth,
7-
} from './HypergraphAppContext.js';
8-
export { HypergraphSpaceProvider } from './HypergraphSpaceContext.js';
9-
export { useCreateEntity } from './hooks/use-create-entity.js';
3+
export { useCreateEntity, useCreateEntityNew } from './hooks/use-create-entity.js';
104
export { useDeleteEntity } from './hooks/use-delete-entity.js';
115
export { useEntity } from './hooks/use-entity.js';
126
export { useHardDeleteEntity } from './hooks/use-hard-delete-entity.js';
137
export { usePrivyAuthCreatePrivateSpace as _usePrivyAuthCreatePrivateSpace } from './hooks/use-privy-auth-create-private-space.js';
148
export { usePrivyAuthCreatePublicSpace as _usePrivyAuthCreatePublicSpace } from './hooks/use-privy-auth-create-public-space.js';
15-
export { useQuery } from './hooks/use-query.js';
9+
export { useQuery, useQueryNew } from './hooks/use-query.js';
1610
export { useRemoveRelation } from './hooks/use-remove-relation.js';
1711
export { useSpace } from './hooks/use-space.js';
1812
export { useSpaces } from './hooks/use-spaces.js';
@@ -23,6 +17,12 @@ export { useOwnAccountInbox } from './hooks/useOwnAccountInbox.js';
2317
export { useOwnSpaceInbox } from './hooks/useOwnSpaceInbox.js';
2418
export { usePublicAccountInboxes } from './hooks/usePublicAccountInboxes.js';
2519
export { usePublishToPublicSpace } from './hooks/usePublishToSpace.js';
20+
export {
21+
HypergraphAppProvider,
22+
useHypergraphApp,
23+
useHypergraphAuth,
24+
} from './HypergraphAppContext.js';
25+
export { HypergraphSpaceProvider } from './HypergraphSpaceContext.js';
2626
export { generateDeleteOps as _generateDeleteOps } from './internal/generate-delete-ops.js';
2727
export { useCreateEntityPublic as _useCreateEntityPublic } from './internal/use-create-entity-public.js';
2828
export { useDeleteEntityPublic as _useDeleteEntityPublic } from './internal/use-delete-entity-public.js';

packages/hypergraph-react/src/internal/use-query-private.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,53 @@ export function useQueryPrivate<const S extends Entity.AnyNoContext>(type: S, pa
5353

5454
return { entities, deletedEntities };
5555
}
56+
57+
type QueryParamsNew<S extends Schema.Schema.AnyNoContext> = {
58+
space?: string | undefined;
59+
enabled: boolean;
60+
filter?: Entity.EntityFilter<Schema.Schema.Type<S>> | undefined;
61+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
62+
};
63+
64+
export function useQueryPrivateNew<const S extends Schema.Schema.AnyNoContext>(type: S, params?: QueryParamsNew<S>) {
65+
const { enabled = true, filter, include, space: spaceFromParams } = params ?? {};
66+
const entitiesRef = useRef<Entity.EntityNew<S>[]>([]);
67+
const subscriptionRef = useRef<Entity.FindManySubscriptionNew<S>>({
68+
subscribe: () => () => undefined,
69+
getEntities: () => entitiesRef.current,
70+
});
71+
const { space: spaceFromContext } = useHypergraphSpaceInternal();
72+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: spaceFromParams ?? spaceFromContext, enabled });
73+
const handleIsReady = handle ? handle.isReady() : false;
74+
75+
// biome-ignore lint/correctness/useExhaustiveDependencies: allow to change filter and include
76+
useLayoutEffect(() => {
77+
if (enabled && handle && handleIsReady) {
78+
const subscription = Entity.subscribeToFindManyNew(handle, type, filter, include);
79+
subscriptionRef.current.subscribe = subscription.subscribe;
80+
subscriptionRef.current.getEntities = subscription.getEntities;
81+
}
82+
}, [enabled, handleIsReady, handle, type]);
83+
84+
// TODO: allow to change the enabled state
85+
const allEntities = useSyncExternalStore(
86+
subscriptionRef.current.subscribe,
87+
subscriptionRef.current.getEntities,
88+
() => entitiesRef.current,
89+
);
90+
91+
const { entities, deletedEntities } = useMemo(() => {
92+
const entities: Entity.EntityNew<S>[] = [];
93+
const deletedEntities: Entity.EntityNew<S>[] = [];
94+
for (const entity of allEntities) {
95+
if (entity.__deleted === true) {
96+
deletedEntities.push(entity);
97+
} else {
98+
entities.push(entity);
99+
}
100+
}
101+
return { entities, deletedEntities };
102+
}, [allEntities]);
103+
104+
return { entities, deletedEntities };
105+
}

packages/hypergraph/src/entity/create.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type { DocHandle } from '@automerge/automerge-repo';
22
import * as Schema from 'effect/Schema';
33
import { generateId } from '../utils/generateId.js';
44
import { isRelationField } from '../utils/isRelationField.js';
5-
import { findOne } from './findOne.js';
5+
import { encodeToGrc20Json } from './entity-new.js';
6+
import { findOne, findOneNew } from './findOne.js';
67
import type { AnyNoContext, DocumentContent, DocumentRelation, Entity, Insert } from './types.js';
78

89
/**
@@ -56,3 +57,21 @@ export const create = <const S extends AnyNoContext>(handle: DocHandle<DocumentC
5657
return findOne(handle, type)(entityId) as Entity<S>;
5758
};
5859
};
60+
61+
export const createNew = <const S extends Schema.Schema.AnyNoContext>(handle: DocHandle<DocumentContent>, type: S) => {
62+
// TODO: return type
63+
return (data: Readonly<Schema.Schema.Type<S>>) => {
64+
const entityId = generateId();
65+
const encoded = encodeToGrc20Json(type, { ...data, id: entityId });
66+
67+
handle.change((doc) => {
68+
doc.entities ??= {};
69+
doc.entities[entityId] = {
70+
...encoded,
71+
__deleted: false,
72+
};
73+
});
74+
75+
return findOneNew(handle, type)(entityId);
76+
};
77+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type * as Schema from 'effect/Schema';
2+
import type { EntityNew } from './types.js';
3+
4+
export type QueryEntry = {
5+
data: Array<EntityNew<Schema.Schema.AnyNoContext>>; // holds the decoded entities of this query and must be a stable reference and use the same reference for the `entities` array
6+
listeners: Array<() => void>; // listeners to this query
7+
isInvalidated: boolean;
8+
include: { [K in keyof Schema.Schema.Type<Schema.Schema.AnyNoContext>]?: Record<string, Record<string, never>> };
9+
};
10+
11+
export type DecodedEntitiesCacheEntry = {
12+
type: Schema.Schema.AnyNoContext; // TODO should be the type of the entity
13+
entities: Map<string, EntityNew<Schema.Schema.AnyNoContext>>; // holds all entities of this type
14+
queries: Map<
15+
string, // instead of serializedQueryKey as string we could also have the actual params
16+
QueryEntry
17+
>;
18+
isInvalidated: boolean;
19+
};
20+
21+
/*
22+
/*
23+
* Note: Currently we only use one global cache for all entities.
24+
* In the future we probably want a build function that creates a cache and returns the
25+
* functions (create, update, findMany, …) that use this specific cache.
26+
*
27+
* How does it work?
28+
*
29+
* We store all decoded entities in a cache and for each query we reference the entities relevant to this query.
30+
* Whenever a query is registered we add it to the cache and add a listener to the query. Whenever a query is unregistered we remove the listener from the query.
31+
*/
32+
type DecodedEntitiesCache = Map<
33+
string, // serialized type IDs
34+
DecodedEntitiesCacheEntry
35+
>;
36+
37+
export const decodedEntitiesCache: DecodedEntitiesCache = new Map();

0 commit comments

Comments
 (0)