Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/pretty-showers-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphprotocol/hypergraph-react": patch
"@graphprotocol/hypergraph": patch
---

add Type.optional

1 change: 1 addition & 0 deletions apps/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@noble/hashes": "^1.8.0",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.2",
"@tanstack/react-query": "^5.75.5",
"@tanstack/react-router": "^1.120.2",
Expand Down
18 changes: 16 additions & 2 deletions apps/events/src/components/events/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useState } from 'react';
import { Event } from '../../schema';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { Label } from '../ui/label';

export const Events = () => {
const { data: eventsLocalData } = useQuery(Event, { mode: 'private' });
Expand Down Expand Up @@ -43,6 +44,7 @@ export const Events = () => {
{eventsLocalData.map((event) => (
<div key={event.id} className="flex flex-row items-center gap-2">
<h2>{event.name}</h2>
<p>{event.description}</p>
<div className="text-xs">{event.id}</div>
<select
value={selectedSpace}
Expand All @@ -66,10 +68,22 @@ export const Events = () => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const name = formData.get('name') as string;
createEvent({ name });
const description = formData.get('description') as string;
if (!name) {
alert('Name is required');
return;
}
if (description) {
createEvent({ name, description });
} else {
createEvent({ name });
}
}}
>
<Input type="text" name="name" />
<Label htmlFor="name">Name</Label>
<Input type="text" name="name" required />
<Label htmlFor="description">Description</Label>
<Input type="text" name="description" />
<Button type="submit">Create Event</Button>
</form>
</>
Expand Down
2 changes: 1 addition & 1 deletion apps/events/src/components/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const Playground = ({ spaceId }: { spaceId: string }) => {
jobOffers: {},
},
},
first: 2,
first: 10,
space: spaceId,
});
const [isDeleting, setIsDeleting] = useState(false);
Expand Down
19 changes: 19 additions & 0 deletions apps/events/src/components/ui/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as LabelPrimitive from '@radix-ui/react-label';
import type * as React from 'react';

import { cn } from '@/lib/utils';

function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
);
}

export { Label };
1 change: 1 addition & 0 deletions apps/events/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const mapping: Mapping.Mapping = {
typeIds: [Id.Id('7f9562d4-034d-4385-bf5c-f02cdebba47a')],
properties: {
name: Id.Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
description: Id.Id('9b1f76ff-9711-404c-861e-59dc3fa7d037'),
},
relations: {
sponsors: Id.Id('6860bfac-f703-4289-b789-972d0aaf3abe'),
Expand Down
2 changes: 1 addition & 1 deletion apps/events/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ export class Company extends Entity.Class<Company>('Company')({

export class Event extends Entity.Class<Event>('Event')({
name: Type.Text,
// description: Type.Text,
description: Type.optional(Type.Text),
sponsors: Type.Relation(Company),
}) {}
14 changes: 14 additions & 0 deletions docs/docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ export class Company extends Entity.Class<Company>('Company')({
}) {}
```

## Optional Fields

You can make a field optional by wrapping it in `Type.optional`.

```ts
import { Entity, Type } from '@graphprotocol/hypergraph';

export class Company extends Entity.Class<Company>('Company')({
name: Type.Text,
description: Type.optional(Type.Text),
founded: Type.optional(Type.Date),
}) {}
```

## Schema Examples

You can search for dozens of schema/mapping examples on the [Hypergraph Schema Browser](https://schema-browser.vercel.app/).
4 changes: 2 additions & 2 deletions packages/hypergraph-react/src/hooks/use-spaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type PublicSpacesQueryResult = {
spaceAddress: string;
page: {
name: string;
};
} | null;
}[];
};

Expand All @@ -43,7 +43,7 @@ export const useSpaces = (params: { mode: 'public' | 'private' }) => {
return result?.spaces
? result.spaces.map((space) => ({
id: space.id,
name: space.page.name,
name: space.page?.name,
spaceAddress: space.spaceAddress,
}))
: [];
Expand Down
16 changes: 11 additions & 5 deletions packages/hypergraph-react/src/internal/use-create-entity-public.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Graph, Id, type PropertiesParam, type RelationsParam } from '@graphprotocol/grc-20';
import type { Connect, Entity } from '@graphprotocol/hypergraph';
import { store, Type } from '@graphprotocol/hypergraph';
import { store, TypeUtils } from '@graphprotocol/hypergraph';
import { useQueryClient } from '@tanstack/react-query';
import { useSelector } from '@xstate/store/react';
import type * as Schema from 'effect/Schema';
Expand Down Expand Up @@ -33,14 +33,20 @@ export function useCreateEntityPublic<const S extends Entity.AnyNoContext>(
const fields = type.fields;
const values: PropertiesParam = [];
for (const [key, value] of Object.entries(mappingEntry.properties || {})) {
if (data[key] === undefined) {
if (TypeUtils.isOptional(fields[key])) {
continue;
}
throw new Error(`Value for ${key} is undefined`);
}
let serializedValue: string = data[key];
if (fields[key] === Type.Checkbox) {
if (TypeUtils.isCheckboxOrOptionalCheckboxType(fields[key])) {
serializedValue = Graph.serializeCheckbox(data[key]);
} else if (fields[key] === Type.Date) {
} else if (TypeUtils.isDateOrOptionalDateType(fields[key])) {
serializedValue = Graph.serializeDate(data[key]);
} else if (fields[key] === Type.Point) {
} else if (TypeUtils.isPointOrOptionalPointType(fields[key])) {
serializedValue = Graph.serializePoint(data[key]);
} else if (fields[key] === Type.Number) {
} else if (TypeUtils.isNumberOrOptionalNumberType(fields[key])) {
serializedValue = Graph.serializeNumber(data[key]);
}

Expand Down
92 changes: 0 additions & 92 deletions packages/hypergraph-react/src/internal/use-generate-update-ops.tsx

This file was deleted.

10 changes: 5 additions & 5 deletions packages/hypergraph-react/src/internal/use-query-public.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Graph } from '@graphprotocol/grc-20';
import { type Entity, type Mapping, store, Type } from '@graphprotocol/hypergraph';
import { type Entity, type Mapping, store, TypeUtils } from '@graphprotocol/hypergraph';
import { useQuery as useQueryTanstack } from '@tanstack/react-query';
import { useSelector } from '@xstate/store/react';
import * as Either from 'effect/Either';
Expand Down Expand Up @@ -153,16 +153,16 @@ const convertPropertyValue = (
key: string,
type: Entity.AnyNoContext,
) => {
if (type.fields[key] === Type.Checkbox) {
if (TypeUtils.isCheckboxOrOptionalCheckboxType(type.fields[key]) && property.value !== undefined) {
return Boolean(property.value);
}
if (type.fields[key] === Type.Point) {
if (TypeUtils.isPointOrOptionalPointType(type.fields[key]) && property.value !== undefined) {
return property.value;
}
if (type.fields[key] === Type.Date) {
if (TypeUtils.isDateOrOptionalDateType(type.fields[key]) && property.value !== undefined) {
return property.value;
}
if (type.fields[key] === Type.Number) {
if (TypeUtils.isNumberOrOptionalNumberType(type.fields[key]) && property.value !== undefined) {
return Number(property.value);
}
return property.value;
Expand Down
30 changes: 21 additions & 9 deletions packages/hypergraph-react/src/prepare-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
type RelationsParam,
} from '@graphprotocol/grc-20';
import type { Entity } from '@graphprotocol/hypergraph';
import { store, Type } from '@graphprotocol/hypergraph';
import { store, TypeUtils } from '@graphprotocol/hypergraph';
import request, { gql } from 'graphql-request';

export type PreparePublishParams<S extends Entity.AnyNoContext> = {
Expand Down Expand Up @@ -68,14 +68,20 @@ export const preparePublish = async <S extends Entity.AnyNoContext>({

if (data?.entity === null) {
for (const [key, propertyId] of Object.entries(mappingEntry.properties || {})) {
if (entity[key] === undefined) {
if (TypeUtils.isOptional(fields[key])) {
continue;
}
throw new Error(`Value for ${key} is undefined`);
}
let serializedValue: string = entity[key];
if (fields[key] === Type.Checkbox) {
if (TypeUtils.isCheckboxOrOptionalCheckboxType(fields[key])) {
serializedValue = Graph.serializeCheckbox(entity[key]);
} else if (fields[key] === Type.Date) {
} else if (TypeUtils.isDateOrOptionalDateType(fields[key])) {
serializedValue = Graph.serializeDate(entity[key]);
} else if (fields[key] === Type.Point) {
} else if (TypeUtils.isPointOrOptionalPointType(fields[key])) {
serializedValue = Graph.serializePoint(entity[key]);
} else if (fields[key] === Type.Number) {
} else if (TypeUtils.isNumberOrOptionalNumberType(fields[key])) {
serializedValue = Graph.serializeNumber(entity[key]);
}
values.push({ property: propertyId, value: serializedValue });
Expand Down Expand Up @@ -105,14 +111,20 @@ export const preparePublish = async <S extends Entity.AnyNoContext>({

if (data?.entity) {
for (const [key, propertyId] of Object.entries(mappingEntry.properties || {})) {
if (entity[key] === undefined) {
if (TypeUtils.isOptional(fields[key])) {
continue;
}
throw new Error(`Value for ${key} is undefined`);
}
let serializedValue: string = entity[key];
if (fields[key] === Type.Checkbox) {
if (TypeUtils.isCheckboxOrOptionalCheckboxType(fields[key])) {
serializedValue = Graph.serializeCheckbox(entity[key]);
} else if (fields[key] === Type.Date) {
} else if (TypeUtils.isDateOrOptionalDateType(fields[key])) {
serializedValue = Graph.serializeDate(entity[key]);
} else if (fields[key] === Type.Point) {
} else if (TypeUtils.isPointOrOptionalPointType(fields[key])) {
serializedValue = Graph.serializePoint(entity[key]);
} else if (fields[key] === Type.Number) {
} else if (TypeUtils.isNumberOrOptionalNumberType(fields[key])) {
serializedValue = Graph.serializeNumber(entity[key]);
}

Expand Down
Loading
Loading