Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
79e51cf
migrate to new schema
nikgraf Sep 23, 2025
ab111b1
wip
nikgraf Sep 23, 2025
f8b1d3f
re-enable relations
nikgraf Sep 25, 2025
05c9074
move symbols
nikgraf Sep 25, 2025
0c10528
implement Type.optional
nikgraf Sep 25, 2025
bdacabc
move from new function/files to core
nikgraf Sep 25, 2025
3cece10
fix relation type
nikgraf Sep 29, 2025
bcc83ac
integrate public queryies without relations
nikgraf Sep 30, 2025
4afe2cc
handle relations in public query
nikgraf Sep 30, 2025
36ce1dc
handle nested relations
nikgraf Oct 6, 2025
b203745
implement useEntity public
nikgraf Oct 6, 2025
e5eee3d
re-implement filters
nikgraf Oct 6, 2025
5324cc8
cleanup
nikgraf Oct 6, 2025
64ba7f4
cleanup imports
nikgraf Oct 6, 2025
21463da
improve types
nikgraf Oct 7, 2025
61ceb22
fix type issues
nikgraf Oct 7, 2025
6267464
fix types
nikgraf Oct 7, 2025
e965c6d
implement prepare publish
nikgraf Oct 7, 2025
dc9cc6b
remove mapping and fix types
nikgraf Oct 7, 2025
bb94bc6
cleanup
nikgraf Oct 7, 2025
483af13
handle optional properties
nikgraf Oct 7, 2025
c7f4170
cleanup
nikgraf Oct 7, 2025
9a7bc0b
fix type
nikgraf Oct 8, 2025
741cd85
lint fixes
nikgraf Oct 8, 2025
7434d4e
improve types
nikgraf Oct 8, 2025
b885d3c
cleanup
nikgraf Oct 8, 2025
fad78d6
fix type
nikgraf Oct 8, 2025
0aceb0b
fix tests
nikgraf Oct 8, 2025
d84c16b
remove __version property
nikgraf Oct 8, 2025
eed2b33
fix invalidation
nikgraf Oct 8, 2025
8084aa1
fix live query updates
nikgraf Oct 8, 2025
aa1b5e8
fix adding multiple relations
nikgraf Oct 8, 2025
5ec85f3
fix tests
nikgraf Oct 9, 2025
1c820b2
fix preparePublish
nikgraf Oct 9, 2025
c9afc3d
fix types
nikgraf Oct 9, 2025
56e4e3b
add changeset
nikgraf Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/icy-emus-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"create-hypergraph": minor
"@graphprotocol/hypergraph-react": minor
"@graphprotocol/hypergraph": minor
---

Schema Definition API Change (Breaking Change)

Before:
```ts
export class User extends Entity.Class<User>('User')({
name: Type.String,
}) {}
```

After:
```ts
export const User = EntitySchema(
{ name: Type.String },
{
types: [Id('bffa181e-a333-495b-949c-57f2831d7eca')],
properties: {
name: Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
},
},
);
```

All entity definitions need to be rewritten. The new API requires explicit type IDs and property IDs.
7 changes: 1 addition & 6 deletions apps/events/src/Boot.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { HypergraphAppProvider } from '@graphprotocol/hypergraph-react';
import { createRouter, RouterProvider } from '@tanstack/react-router';
import { mapping } from './mapping.js';
import { routeTree } from './routeTree.gen';

// Create a new router instance
Expand All @@ -15,11 +14,7 @@ declare module '@tanstack/react-router' {

export function Boot() {
return (
<HypergraphAppProvider
syncServerUri="http://localhost:3030"
mapping={mapping}
appId="93bb8907-085a-4a0e-83dd-62b0dc98e793"
>
<HypergraphAppProvider syncServerUri="http://localhost:3030" appId="93bb8907-085a-4a0e-83dd-62b0dc98e793">
<RouterProvider router={router} />
</HypergraphAppProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/events/src/components/event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Event = ({ spaceId, entityId }: { spaceId: string; entityId: string
space: spaceId,
});

console.log({ component: 'Event', isPending, isError, data });
// console.log({ component: 'Event', isPending, isError, data });

return (
<div>
Expand Down
3 changes: 2 additions & 1 deletion apps/events/src/components/events/events.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Entity } from '@graphprotocol/hypergraph';
import {
preparePublish,
publishOps,
Expand All @@ -19,7 +20,7 @@ export const Events = () => {
const { data: spaces } = useSpaces({ mode: 'public' });
const [selectedSpace, setSelectedSpace] = useState<string>('');

const handlePublish = async (event: Event) => {
const handlePublish = async (event: Entity.Entity<typeof Event>) => {
if (!selectedSpace) {
alert('No space selected');
return;
Expand Down
44 changes: 7 additions & 37 deletions apps/events/src/components/playground.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,37 @@
import {
_useCreateEntityPublic,
_useDeleteEntityPublic,
useHypergraphApp,
useQuery,
useSpace,
} from '@graphprotocol/hypergraph-react';
import { _useDeleteEntityPublic, useHypergraphApp, useQuery, useSpace } from '@graphprotocol/hypergraph-react';
import { useState } from 'react';
import { Event } from '../schema';
import { Button } from './ui/button';

export const Playground = ({ spaceId }: { spaceId: string }) => {
const { data, isLoading, isError } = useQuery(Event, {
const { data, isLoading, isError, invalidEntities } = useQuery(Event, {
mode: 'public',
include: {
sponsors: {
jobOffers: {},
},
},
// filter: {
// or: [{ name: { startsWith: 'test' } }, { name: { startsWith: 'ETH' } }],
// },
filter: {
or: [{ name: { startsWith: 'My' } }, { name: { startsWith: 'ETH' } }],
},
// filter: { name: { startsWith: 'My Test Event' } },
first: 100,
space: spaceId,
});
const [isDeleting, setIsDeleting] = useState(false);
const [isCreating, setIsCreating] = useState(false);
const { getSmartSessionClient } = useHypergraphApp();

const { name } = useSpace({ mode: 'public', space: spaceId });

const deleteEntity = _useDeleteEntityPublic(Event, { space: spaceId });
const createEntity = _useCreateEntityPublic(Event, { space: spaceId });

console.log({ isLoading, isError, data });
console.log({ isLoading, isError, data, invalidEntities });

return (
<div>
<h2 className="text-lg font-bold">Space: {name}</h2>
{isLoading && <div>Loading...</div>}
{isError && <div>Error</div>}
<Button
disabled={isCreating}
onClick={async () => {
setIsCreating(true);
const walletClient = await getSmartSessionClient();
if (!walletClient) {
alert('Wallet client not found');
setIsCreating(false);
return;
}
const { success, cid, txResult } = await createEntity(
{
name: 'Test Event 42 by Nik',
sponsors: [],
},
{ walletClient },
);
console.log('created', { success, cid, txResult });
setIsCreating(false);
}}
>
Create
</Button>
{data?.map((event) => (
<div key={event.id}>
<h2>{event.name}</h2>
Expand Down
33 changes: 33 additions & 0 deletions apps/events/src/components/projects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useQuery } from '@graphprotocol/hypergraph-react';
import { Project } from '../schema';

export const Projects = ({ spaceId }: { spaceId: string }) => {
const { data, isLoading, isError } = useQuery(Project, {
mode: 'public',
// include: {
// sponsors: {
// jobOffers: {},
// },
// },
// filter: {
// or: [{ name: { startsWith: 'test' } }, { name: { startsWith: 'ETH' } }],
// },
first: 100,
space: spaceId,
});
console.log({ isLoading, isError, data });

return (
<div>
<h2 className="text-lg font-bold">Projects</h2>
{isLoading && <div>Loading...</div>}
{isError && <div>Error</div>}
{data?.map((project) => (
<div key={project.id}>
<h2>{project.name}</h2>
<pre className="text-xs">{JSON.stringify(project, null, 2)}</pre>
</div>
))}
</div>
);
};
2 changes: 0 additions & 2 deletions apps/events/src/components/todo/todos-local.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export const TodosLocal = () => {
/>
{/* @ts-expect-error */}
<div className="text-xs">{todo.__deleted ? 'deleted' : 'not deleted'}</div>
{/* @ts-expect-error */}
<div className="text-xs">{todo.__version}</div>
<Button variant="secondary" size="sm" onClick={() => hardDeleteEntity(todo.id)}>
Hard Delete
</Button>
Expand Down
112 changes: 2 additions & 110 deletions apps/events/src/components/todos2.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
import type { PublishDiffInfo } from '@graphprotocol/hypergraph-react';
import {
PublishDiff,
publishOps,
useCreateEntity,
useDeleteEntity,
useHypergraphApp,
useQuery,
useSpace,
useUpdateEntity,
} from '@graphprotocol/hypergraph-react';
import { useQueryClient } from '@tanstack/react-query';
import { useCreateEntity, useDeleteEntity, useQuery, useSpace, useUpdateEntity } from '@graphprotocol/hypergraph-react';
import { useEffect, useState } from 'react';
import Select from 'react-select';
import { cn } from '@/lib/utils';
Expand All @@ -19,7 +8,6 @@ import { TodosLocal } from './todo/todos-local';
import { TodosPublic } from './todo/todos-public';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Modal } from './ui/modal';

export const Todos2 = () => {
const {
Expand All @@ -34,20 +22,14 @@ export const Todos2 = () => {
isError: isErrorUsers,
// preparePublish: preparePublishUsers,
} = useQuery(User, { mode: 'private' });
const { ready: spaceReady, id: spaceId } = useSpace({ mode: 'private' });
const { getSmartSessionClient } = useHypergraphApp();
const { ready: spaceReady } = useSpace({ mode: 'private' });
const createTodo = useCreateEntity(Todo2);
const updateTodo = useUpdateEntity(Todo2);
const createUser = useCreateEntity(User);
const deleteEntity = useDeleteEntity();
const [newTodoName, setNewTodoName] = useState('');
const [newTodoAssignees, setNewTodoAssignees] = useState<{ value: string; label: string }[]>([]);
const [newUserName, setNewUserName] = useState('');
const queryClient = useQueryClient();
const [publishData, setPublishData] = useState<PublishDiffInfo | null>(null);
const [isPublishDiffModalOpen, setIsPublishDiffModalOpen] = useState(false);
const [isPreparingPublish, setIsPreparingPublish] = useState(false);
const [isPublishing, setIsPublishing] = useState(false);

useEffect(() => {
setNewTodoAssignees((prevFilteredAssignees) => {
Expand All @@ -74,8 +56,6 @@ export const Todos2 = () => {
<div key={user.id} className="flex flex-row items-center gap-2">
<h2>{user.name}</h2>
<div className="text-xs">{user.id}</div>
{/* @ts-expect-error */}
<div className="text-xs">{user.__version}</div>
<Button variant="outline" size="sm" onClick={() => deleteEntity(user.id)}>
Delete
</Button>
Expand Down Expand Up @@ -140,8 +120,6 @@ export const Todos2 = () => {
))}
</span>
)}
{/* @ts-expect-error */}
<div className="text-xs">{todo.__version}</div>
<Button variant="outline" size="sm" onClick={() => deleteEntity(todo.id)}>
Delete
</Button>
Expand Down Expand Up @@ -179,92 +157,6 @@ export const Todos2 = () => {
</Button>
</div>

<Button
onClick={async () => {
try {
setIsPreparingPublish(true);
// const usersResult = await preparePublishUsers();
// console.log('users ops & diff', usersResult);
// const todosResult = await preparePublishTodos();
// console.log('todos ops & diff', todosResult);

// if (todosResult && usersResult) {
// setPublishData({
// newEntities: [...todosResult.newEntities, ...usersResult.newEntities],
// deletedEntities: [...todosResult.deletedEntities, ...usersResult.deletedEntities],
// updatedEntities: [...todosResult.updatedEntities, ...usersResult.updatedEntities],
// });
// setIsPublishDiffModalOpen(true);
// } else {
// console.error('preparing publishing error', todosResult, usersResult);
// throw new Error('Failed to prepare the publishing operations');
// }
} catch (error) {
console.error('preparing publishing error', error);
alert('Failed to prepare the publishing operations');
} finally {
setIsPreparingPublish(false);
}
}}
disabled={isPreparingPublish}
>
{isPreparingPublish ? 'Preparing …' : 'Prepare Publish'}
</Button>

<Modal isOpen={isPublishDiffModalOpen} onOpenChange={setIsPublishDiffModalOpen}>
<div className="p-4 flex flex-col gap-4 min-w-96">
<PublishDiff
newEntities={publishData?.newEntities ?? []}
deletedEntities={publishData?.deletedEntities ?? []}
updatedEntities={publishData?.updatedEntities ?? []}
/>
<Button
onClick={async () => {
try {
const smartSessionClient = await getSmartSessionClient();
if (!smartSessionClient) {
throw new Error('Missing smartSessionClient');
}
if (publishData) {
setIsPublishing(true);
const ops = [
...publishData.newEntities.map((entity) => entity.ops),
...publishData.updatedEntities.map((entity) => entity.ops),
...publishData.deletedEntities.map((entity) => entity.ops),
].flat();
const publishOpsResult = await publishOps({
ops,
walletClient: smartSessionClient,
space: spaceId,
name: 'Update users and todos',
});
console.log('publishOpsResult', publishOpsResult);
setIsPublishDiffModalOpen(false);
setPublishData(null);
setTimeout(() => {
queryClient.invalidateQueries({
queryKey: ['hypergraph-public-entities', Todo2.name],
});
}, 1000);
}
} catch (error) {
console.error('publishing error', error);
} finally {
setIsPublishing(false);
}
}}
disabled={
(publishData?.newEntities.length === 0 &&
publishData?.updatedEntities.length === 0 &&
publishData?.deletedEntities.length === 0) ||
isPublishing
}
>
{isPublishing ? 'Publishing …' : 'Publish'}
</Button>
</div>
</Modal>

<TodosLocal />

<TodosPublic />
Expand Down
3 changes: 2 additions & 1 deletion apps/events/src/components/user-entry.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Entity } from '@graphprotocol/hypergraph';
import { useDeleteEntity, useUpdateEntity } from '@graphprotocol/hypergraph-react';
import { useState } from 'react';
import { User } from '../schema.js';
import { Button } from './ui/button';
import { Input } from './ui/input.js';

export const UserEntry = (user: User & { id: string }) => {
export const UserEntry = (user: Entity.Entity<typeof User> & { id: string }) => {
const deleteEntity = useDeleteEntity();
const updateEntity = useUpdateEntity(User);
const [editMode, setEditMode] = useState(false);
Expand Down
4 changes: 2 additions & 2 deletions apps/events/src/components/users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { UserEntry } from './user-entry.js';
export const Users = () => {
const { data: users } = useQuery(User, { mode: 'private' });
const { ready: spaceReady } = useSpace({ mode: 'private' });
const createEntity = useCreateEntity(User);
const createEntityNew = useCreateEntity(User);
const [newUserName, setNewUserName] = useState('');

if (!spaceReady) {
Expand All @@ -22,7 +22,7 @@ export const Users = () => {
<Input type="text" value={newUserName} onChange={(e) => setNewUserName(e.target.value)} />
<Button
onClick={() => {
createEntity({ name: newUserName });
createEntityNew({ name: newUserName });
setNewUserName('');
}}
>
Expand Down
2 changes: 0 additions & 2 deletions apps/events/src/components/users/users-local.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ export const UsersLocal = () => {
<div className="text-xs">{user.id}</div>
{/* @ts-expect-error */}
<div className="text-xs">{user.__deleted ? 'deleted' : 'not deleted'}</div>
{/* @ts-expect-error */}
<div className="text-xs">{user.__version}</div>
<Button variant="secondary" size="sm" onClick={() => hardDeleteEntity(user.id)}>
Hard Delete
</Button>
Expand Down
Loading
Loading