Skip to content

Commit f317c1f

Browse files
authored
feat(typesync): update to latest dep versions. use new hypergraph types (#202)
1 parent 82b867a commit f317c1f

File tree

25 files changed

+2092
-1165
lines changed

25 files changed

+2092
-1165
lines changed

.DS_Store

8 KB
Binary file not shown.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ lerna-debug.log*
1616
# Local database
1717
*.db
1818
*.db-journal
19+
*.db-shm
20+
*.db-wal
1921

2022
# Runtime data
2123
pids

apps/connect/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@tanstack/react-router": "^1.120.2",
1818
"class-variance-authority": "^0.7.1",
1919
"clsx": "^2.1.1",
20-
"effect": "^3.14.20",
20+
"effect": "^3.16.3",
2121
"framer-motion": "^12.10.1",
2222
"lucide-react": "^0.508.0",
2323
"react": "^19.1.0",

apps/events/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@xstate/store": "^3.5.1",
2222
"class-variance-authority": "^0.7.1",
2323
"clsx": "^2.1.1",
24-
"effect": "^3.14.20",
24+
"effect": "^3.16.3",
2525
"framer-motion": "^12.10.1",
2626
"graphql-request": "^7.1.2",
2727
"isomorphic-ws": "^5.0.0",

apps/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@noble/ciphers": "^1.3.0",
1414
"@prisma/client": "^6.7.0",
1515
"cors": "^2.8.5",
16-
"effect": "^3.14.20",
16+
"effect": "^3.16.3",
1717
"express": "^5.1.0",
1818
"siwe": "^3.0.0",
1919
"viem": "^2.29.0",

apps/typesync/client/src/Components/App/SchemaBuilder/SchemaBuilder.tsx

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,22 @@
33
import { Input } from '@headlessui/react';
44
import { ExclamationCircleIcon, PlusIcon } from '@heroicons/react/16/solid';
55
import { TrashIcon } from '@heroicons/react/24/outline';
6-
import { effectTsResolver } from '@hookform/resolvers/effect-ts';
6+
import { Array as EffectArray, pipe } from 'effect';
77
import {
88
type Control,
99
type UseFormRegister,
1010
type UseFormSetValue,
1111
useFieldArray,
12-
useForm,
12+
useFormContext,
1313
useWatch,
1414
} from 'react-hook-form';
1515

16+
import type { InsertAppSchema } from '../../../schema.js';
1617
import { SchemaBrowser } from './SchemaBrowser.js';
1718
import { TypeCombobox } from './TypeCombobox.js';
18-
import { AppSchemaForm } from './types.js';
19-
20-
// biome-ignore lint/suspicious/noExplicitAny: appears to be an issue with the effectTsResolver
21-
type HookformEffectSchema = any;
2219

2320
export function SchemaBuilder() {
24-
const { control, register, formState, setValue } = useForm<AppSchemaForm>({
25-
resolver: effectTsResolver(AppSchemaForm as HookformEffectSchema),
26-
defaultValues: {
27-
types: [{ name: '', properties: [{ name: '', typeName: 'Text' }] }],
28-
},
29-
shouldFocusError: true,
30-
});
21+
const { control, register, formState, setValue } = useFormContext<InsertAppSchema>();
3122
const typesArray = useFieldArray({
3223
control,
3324
name: 'types',
@@ -36,19 +27,24 @@ export function SchemaBuilder() {
3627
},
3728
});
3829

39-
const schema = useWatch<AppSchemaForm>({
30+
const schema = useWatch<InsertAppSchema>({
4031
control,
4132
exact: true,
4233
});
34+
const schemaTypes = pipe(
35+
schema.types ?? [],
36+
EffectArray.filter((_type) => _type.name != null),
37+
EffectArray.map((_type) => _type.name || ''),
38+
);
4339

4440
return (
45-
<div className="grid grid-cols-2 lg:grid-cols-7 gap-x-4">
41+
<div className="grid grid-cols-2 lg:grid-cols-7 gap-x-4 pb-16">
4642
<div className="lg:col-span-4 flex flex-col gap-y-4">
4743
<div className="border-b border-gray-200 dark:border-white/20 pb-5">
4844
<h3 className="text-base font-semibold text-gray-900 dark:text-white">Schema</h3>
4945
<p className="mt-2 max-w-4xl text-sm text-gray-500 dark:text-gray-200">
50-
Build your app schema by adding types, fields belonging to those types, etc. View already existing schemas
51-
and types to add to your schema.
46+
Build your app schema by adding types, properties belonging to those types, etc. View already existing
47+
schemas, types and properties to add to your schema.
5248
</p>
5349
</div>
5450
{typesArray.fields.map((_type, idx) => (
@@ -110,14 +106,29 @@ export function SchemaBuilder() {
110106
<TrashIcon aria-hidden="true" className="size-5" />
111107
</button>
112108
</div>
113-
<PropsInput control={control} register={register} typeIndex={idx} setValue={setValue} />
109+
<PropsInput
110+
control={control}
111+
register={register}
112+
typeIndex={idx}
113+
setValue={setValue}
114+
schemaTypes={EffectArray.filter(schemaTypes, (_typeName) => {
115+
// filter out this type
116+
const schemaTypeNameAtIdx = schema.types?.[idx]?.name;
117+
return schemaTypeNameAtIdx != null && schemaTypeNameAtIdx !== _typeName;
118+
})}
119+
/>
114120
</div>
115121
))}
116122
<div className="w-full flex items-center justify-end border-t border-gray-500 dark:border-gray-400 mt-3">
117123
<button
118124
type="button"
119125
className="inline-flex items-center gap-x-1.5 text-sm/6 font-semibold text-gray-900 dark:text-white cursor-pointer hover:bg-gray-100 dark:hover:bg-white/10 rounded-md px-2 py-1.5"
120-
onClick={() => typesArray.append({ name: '', properties: [] })}
126+
onClick={() =>
127+
typesArray.append({
128+
name: '',
129+
properties: [{ name: '', type_name: 'Text' }],
130+
})
131+
}
121132
>
122133
<PlusIcon aria-hidden="true" className="-ml-0.5 size-5" />
123134
Add Type
@@ -135,7 +146,10 @@ export function SchemaBuilder() {
135146
name: type.name || '',
136147
properties: type.properties.map((prop) => ({
137148
name: prop.name || '',
138-
typeName: prop.valueType?.name ?? 'Text',
149+
type_name: prop.valueType?.name ?? 'Text',
150+
description: null,
151+
optional: null,
152+
nullable: null,
139153
})),
140154
});
141155
}}
@@ -147,10 +161,17 @@ export function SchemaBuilder() {
147161

148162
function PropsInput(
149163
props: Readonly<{
150-
control: Control<AppSchemaForm>;
151-
register: UseFormRegister<AppSchemaForm>;
164+
control: Control<InsertAppSchema>;
165+
register: UseFormRegister<InsertAppSchema>;
152166
typeIndex: number;
153-
setValue: UseFormSetValue<AppSchemaForm>;
167+
setValue: UseFormSetValue<InsertAppSchema>;
168+
/**
169+
* A list of types within the defined schema that the user can use as a relation
170+
* This allows the user to specify the property as a relationship to a type in the schema
171+
*
172+
* @default []
173+
*/
174+
schemaTypes?: Array<string>;
154175
}>,
155176
) {
156177
const typePropertiesArray = useFieldArray({
@@ -160,7 +181,7 @@ function PropsInput(
160181
// this is annoying, but the control register is not picking up changes in the <Combobox> headless-ui type.
161182
// so, instead, grabbing the value and use the onChange to set in the form.
162183
// @todo FIX THIS
163-
const typeProperties = useWatch<AppSchemaForm>({
184+
const typeProperties = useWatch<InsertAppSchema>({
164185
control: props.control,
165186
exact: true,
166187
});
@@ -184,8 +205,9 @@ function PropsInput(
184205
<TypeCombobox
185206
typeIdx={props.typeIndex}
186207
typePropertyIdx={idx}
187-
value={thisType?.properties?.[idx]?.typeName || 'Text'}
208+
value={thisType?.properties?.[idx]?.type_name || 'Text'}
188209
onTypeSelected={props.setValue}
210+
schemaTypes={props.schemaTypes}
189211
/>
190212
</div>
191213
<div className="col-span-1 flex items-center justify-end">
@@ -204,7 +226,7 @@ function PropsInput(
204226
<button
205227
type="button"
206228
className="inline-flex items-center gap-x-1.5 text-sm/4 font-semibold text-gray-900 dark:text-white cursor-pointer hover:bg-gray-100 dark:hover:bg-white/10 rounded-md px-2 py-1.5"
207-
onClick={() => typePropertiesArray.append({ name: '', typeName: 'Text' })}
229+
onClick={() => typePropertiesArray.append({ name: '', type_name: 'Text' })}
208230
>
209231
<PlusIcon aria-hidden="true" className="-ml-0.5 size-4" />
210232
Add Property

apps/typesync/client/src/Components/App/SchemaBuilder/TypeCombobox.tsx

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,70 @@
22

33
import { Label, Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react';
44
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/16/solid';
5-
import { Schema } from 'effect';
5+
import { Array as EffectArray, String as EffectString, Schema, pipe } from 'effect';
66
import type { UseFormSetValue } from 'react-hook-form';
77

8-
import type { AppSchemaForm } from './types.js';
8+
import type { InsertAppSchema } from '../../../schema.js';
9+
import { classnames } from '../../../utils/classnames.js';
910

10-
class TypeOptionResult extends Schema.Class<TypeOptionResult>('TypeOptionResult')({
11+
class TypeOption extends Schema.Class<TypeOption>('/hypergraph/typesync/models/TypeOption')({
1112
id: Schema.NonEmptyTrimmedString,
1213
name: Schema.NonEmptyTrimmedString,
1314
}) {}
15+
class RelationTypeOption extends Schema.Class<RelationTypeOption>('/hypergraph/typesync/models/RelationTypeOption')({
16+
...TypeOption.fields,
17+
relationToEntity: Schema.NonEmptyTrimmedString,
18+
}) {}
1419

15-
const typeOptions: Array<TypeOptionResult> = [
16-
TypeOptionResult.make({ id: 'DefaultEntityText', name: 'Text' }),
17-
TypeOptionResult.make({ id: 'DefaultEntityNumber', name: 'Number' }),
18-
TypeOptionResult.make({ id: 'DefaultEntityCheckbox', name: 'Checkbox' }),
20+
const typeOptions: Array<TypeOption> = [
21+
TypeOption.make({ id: 'DefaultEntityText', name: 'Text' }),
22+
TypeOption.make({ id: 'DefaultEntityNumber', name: 'Number' }),
23+
TypeOption.make({ id: 'DefaultEntityBoolean', name: 'Boolean' }),
24+
TypeOption.make({ id: 'DefaultEntityDate', name: 'Date' }),
25+
TypeOption.make({ id: 'DefaultEntityUrl', name: 'Url' }),
26+
TypeOption.make({ id: 'DefaultEntityPoint', name: 'Point' }),
1927
];
2028

21-
export function TypeCombobox(
22-
props: Readonly<{
23-
// the index of this type selection field in the properties array. Types.AppSchemaForm.types[idx].properties[typeInputIdx]
24-
typePropertyIdx: number;
25-
// the index of the type within the schema array Types.AppSchemaForm.types[typeIdx]
26-
typeIdx: number;
27-
// the current value
28-
value: string;
29-
// set the value in the form when the user selects a value
30-
onTypeSelected: UseFormSetValue<AppSchemaForm>;
31-
}>,
32-
) {
29+
export function TypeCombobox({
30+
typePropertyIdx,
31+
typeIdx,
32+
value,
33+
onTypeSelected,
34+
schemaTypes = [],
35+
}: Readonly<{
36+
/** the index of this type selection field in the properties array. Types.AppSchemaForm.types[idx].properties[typeInputIdx] */
37+
typePropertyIdx: number;
38+
/** the index of the type within the schema array Types.AppSchemaForm.types[typeIdx] */
39+
typeIdx: number;
40+
/** the current value */
41+
value: string;
42+
/** set the value in the form when the user selects a value */
43+
onTypeSelected: UseFormSetValue<InsertAppSchema>;
44+
/**
45+
* A list of types within the defined schema that the user can use as a relation
46+
* This allows the user to specify the property as a relationship to a type in the schema
47+
*
48+
* @default []
49+
*/
50+
schemaTypes?: Array<string> | undefined;
51+
}>) {
52+
const relationTypeOptions = pipe(
53+
schemaTypes,
54+
EffectArray.filter((_type) => EffectString.isNonEmpty(_type)),
55+
EffectArray.map((_type) =>
56+
RelationTypeOption.make({ id: `Relation(${_type})`, name: `Relation(${_type})`, relationToEntity: _type }),
57+
),
58+
);
59+
3360
return (
3461
<Listbox
3562
as="div"
36-
id={`types.${props.typeIdx}.properties.${props.typePropertyIdx}.typeName`}
37-
name={`types.${props.typeIdx}.properties.${props.typePropertyIdx}.typeName`}
38-
value={props.value}
63+
id={`types.${typeIdx}.properties.${typePropertyIdx}.type_name`}
64+
name={`types.${typeIdx}.properties.${typePropertyIdx}.type_name`}
65+
value={value}
3966
onChange={(value) => {
4067
if (value) {
41-
props.onTypeSelected(`types.${props.typeIdx}.properties.${props.typePropertyIdx}.typeName`, value, {
68+
onTypeSelected(`types.${typeIdx}.properties.${typePropertyIdx}.type_name`, value, {
4269
shouldDirty: true,
4370
shouldTouch: true,
4471
shouldValidate: true,
@@ -49,7 +76,7 @@ export function TypeCombobox(
4976
<Label className="sr-only">Prop type</Label>
5077
<div className="relative">
5178
<ListboxButton className="grid w-full cursor-default grid-cols-1 rounded-md bg-slate-900 py-1.5 pr-2 pl-3 text-left text-white outline-1 -outline-offset-1 outline-gray-500 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
52-
<span className="col-start-1 row-start-1 truncate pr-6">{props.value}</span>
79+
<span className="col-start-1 row-start-1 truncate pr-6">{value}</span>
5380
<ChevronUpDownIcon
5481
aria-hidden="true"
5582
className="col-start-1 row-start-1 size-5 self-center justify-self-end text-gray-300 sm:size-4"
@@ -68,6 +95,22 @@ export function TypeCombobox(
6895
>
6996
<span className="block truncate font-normal group-data-selected:font-semibold">{type.name}</span>
7097

98+
<span className="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 group-not-data-selected:hidden group-data-focus:text-white">
99+
<CheckIcon aria-hidden="true" className="size-5" />
100+
</span>
101+
</ListboxOption>
102+
))}
103+
{relationTypeOptions.map((type, idx) => (
104+
<ListboxOption
105+
key={type.id}
106+
value={type.name}
107+
className={classnames(
108+
'group relative cursor-default py-2 pr-9 pl-3 text-white select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden',
109+
idx === 0 ? 'border-t border-gray-400 dark:border-white/10' : '',
110+
)}
111+
>
112+
<span className="block truncate font-normal group-data-selected:font-semibold">{type.name}</span>
113+
71114
<span className="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 group-not-data-selected:hidden group-data-focus:text-white">
72115
<CheckIcon aria-hidden="true" className="size-5" />
73116
</span>

apps/typesync/client/src/Components/App/SchemaBuilder/types.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
import * as Schema from 'effect/Schema';
22

3-
// default schema types
4-
export const Text = Schema.String;
5-
export const SchemaNumber = Schema.Number;
6-
export const Checkbox = Schema.Boolean;
7-
export const SchemaObject = Schema.Object;
8-
9-
export const DefaultSchemaTypes = [Text, SchemaNumber, Checkbox, SchemaObject] as const;
10-
113
export const AppSchemaField = Schema.Struct({
124
name: Schema.NonEmptyTrimmedString,
135
typeName: Schema.NonEmptyTrimmedString,

apps/typesync/client/src/Components/App/SchemaBuilder/utils.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,25 @@ function fieldToEntityString({
1212

1313
// Convert type to Entity type
1414
const entityType = (() => {
15-
switch (typeName) {
16-
case 'Text':
17-
return 'Entity.Text';
18-
case 'Number':
19-
return 'Entity.Number';
20-
case 'Checkbox':
21-
return 'Entity.Checkbox';
15+
switch (true) {
16+
case typeName === 'Text':
17+
return 'Type.Text';
18+
case typeName === 'Number':
19+
return 'Type.Number';
20+
case typeName === 'Boolean':
21+
return 'Type.Boolean';
22+
case typeName === 'Date':
23+
return 'Type.Date';
24+
case typeName === 'Url':
25+
return 'Type.Url';
26+
case typeName === 'Point':
27+
return 'Type.Point';
28+
case typeName.startsWith('Relation'):
29+
// renders the type as `Type.Relation(Entity)`
30+
return `Type.${typeName}`;
2231
default:
2332
// how to handle complex types
24-
return 'Entity.Any';
33+
return 'Type.Text';
2534
}
2635
})();
2736

@@ -50,7 +59,7 @@ function typeDefinitionToString(type: {
5059
const fieldStrings = fields.map(fieldToEntityString);
5160

5261
const capitalizedName = type.name.charAt(0).toUpperCase() + type.name.slice(1);
53-
return `class ${capitalizedName} extends Entity.Class<${capitalizedName}>('${capitalizedName}')({
62+
return `export class ${capitalizedName} extends Entity.Class<${capitalizedName}>('${capitalizedName}')({
5463
${fieldStrings.join(',\n')}
5564
}) {}`;
5665
}
@@ -77,7 +86,7 @@ ${fieldStrings.join(',\n')}
7786
* expect(code).toEqual(`
7887
* import * as Entity from '@graphprotocol/hypergraph/Entity';
7988
*
80-
* class Event extends Entity.Class<Event>('Event')({
89+
* export class Event extends Entity.Class<Event>('Event')({
8190
* // Name of the event
8291
* name: string;
8392
* description: string | null;
@@ -90,7 +99,7 @@ ${fieldStrings.join(',\n')}
9099
*/
91100
export function buildAppSchemaFormCode(schema: AppSchemaForm): Readonly<{ code: string; hash: string }> {
92101
const fileCommentStatement = '// src/schema.ts';
93-
const importStatement = `import * as Entity from '@graphprotocol/hypergraph/Entity';\nimport * as Schema from 'effect/Schema';`;
102+
const importStatement = `import { Entity, Type } from '@graphprotocol/hypergraph';\nimport * as Schema from 'effect/Schema';`;
94103
const typeDefinitions = schema.types
95104
.map(typeDefinitionToString)
96105
.filter((def) => def != null)

0 commit comments

Comments
 (0)