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
5 changes: 4 additions & 1 deletion apps/typesync/client/src/Components/FormComponents/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export function FormComponentTextField({ id, label, hint, ...rest }: Readonly<Fo
{...rest}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
onChange={(e) => {
field.handleChange(e.target.value);
rest.onChange?.(e);
}}
data-state={hasErrors ? 'invalid' : undefined}
aria-invalid={hasErrors ? 'true' : undefined}
aria-describedby={hasErrors ? `${id}-invalid` : hint != null ? `${id}-hint` : undefined}
Expand Down
26 changes: 8 additions & 18 deletions apps/typesync/client/src/routes/apps/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,30 +366,20 @@ function CreateAppPage() {
<h2 className="text-base/7 font-semibold text-gray-900 dark:text-white">Create New App</h2>
<div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div className="sm:col-span-4">
<createAppForm.AppField
name="name"
listeners={{
onBlur({ value }) {
// set the default value of the directory to be `./${formatted app name}
if (
EffectString.isNonEmpty(value) &&
EffectString.isEmpty(createAppForm.state.values.directory || '')
) {
createAppForm.setFieldValue(
'directory',
`./${pipe(value, EffectString.toLowerCase, EffectString.replaceAll(/\s/g, '-'))}`,
);
}
},
}}
>
<createAppForm.AppField name="name">
{(field) => (
<field.FormComponentTextField
id="name"
name="name"
required
label="App name"
hint="App name must be unique"
onChange={(e) => {
createAppForm.setFieldValue(
'directory',
`./${pipe(e.target.value, EffectString.toLowerCase, EffectString.replaceAll(/\s/g, '-'))}`,
);
}}
Comment on lines +377 to +382
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the listeners object on the createAppForm.AppField? It has an onChange as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no objection to use listener

sadly I'm not familiar with the TypeSync codebase nor TanStack Forms. Is listeners recommended by Tanstack forms or are there other benefits?

/>
)}
</createAppForm.AppField>
Expand Down Expand Up @@ -808,7 +798,7 @@ function CreateAppPage() {
</div>
</TabsPrimitive.Content>

<TabsPrimitive.List className="mt-6 flex items-center justify-end gap-x-6">
<TabsPrimitive.List className="mt-6 flex items-center justify-end gap-x-6 fixed bottom-4 right-4 bg-white dark:bg-black p-4 rounded-lg">
<Link to="/" className="text-sm/6 font-semibold text-gray-900 dark:text-white">
Cancel
</Link>
Expand Down
47 changes: 47 additions & 0 deletions apps/typesync/src/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export class SchemaGenerator extends Effect.Service<SchemaGenerator>()('/typesyn
),
Effect.andThen(() => updatePackageJson(app, directory)),
Effect.andThen(() => fs.writeFileString(path.join(directory, 'src', 'schema.ts'), buildSchemaFile(app))),
Effect.andThen(() => fs.writeFileString(path.join(directory, 'src', 'mapping.ts'), buildMappingFile(app))),
Effect.andThen(() => cleanup),
);

Expand Down Expand Up @@ -290,9 +291,55 @@ ${fieldStrings.join(',\n')}

function buildSchemaFile(schema: Domain.InsertAppSchema) {
const importStatement = `import { Entity, Type } from '@graphprotocol/hypergraph';`;

const typeDefinitions = schema.types
.map(typeDefinitionToString)
.filter((def) => def != null)
.join('\n\n');
return [importStatement, typeDefinitions].join('\n\n');
}

export function buildMappingFile(schema: Domain.InsertAppSchema) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many of the properties won't have knowledgeGraphId if it is a custom property. So I think we should filter out the properties where property.knowledgeGraphId is null, yeah?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spot on, created an issue here #301

can you pick it up? otherwise I do it as soon as I resolved the other urgent tasks

const importStatement1 = `import { Id } from '@graphprotocol/grc-20';`;
const importStatement2 = `import type { Mapping } from '@graphprotocol/hypergraph';`;

const typeMappings: string[] = [];

for (const type of schema.types) {
const properties: string[] = [];
const relations: string[] = [];

// Process properties and relations
for (const property of type.properties) {
if (Domain.isDataTypeRelation(property.dataType)) {
// This is a relation
relations.push(` ${Utils.toCamelCase(property.name)}: Id.Id('${property.knowledgeGraphId}')`);
} else {
// This is a regular property
properties.push(` ${Utils.toCamelCase(property.name)}: Id.Id('${property.knowledgeGraphId}')`);
}
}

const typeMapping = ` ${type.name}: {
typeIds: [Id.Id('${type.knowledgeGraphId}')],
properties: {
${properties.join(',\n')},
},${
relations.length > 0
? `
relations: {
${relations.join(',\n')},
},`
: ''
}
}`;

typeMappings.push(typeMapping);
}

const mappingString = `export const mapping: Mapping = {
${typeMappings.join(',\n')},
};`;

return [importStatement1, importStatement2, '', mappingString].join('\n');
}
79 changes: 79 additions & 0 deletions apps/typesync/test/Generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, expect, it } from '@effect/vitest';

// @ts-ignore - fix the ts setup
import { buildMappingFile } from '../src/Generator.js';

describe('buildMappingFile', () => {
it('should build a valid mapping file', () => {
const expectedMapping = `import { Id } from '@graphprotocol/grc-20';
import type { Mapping } from '@graphprotocol/hypergraph';

export const mapping: Mapping = {
Space: {
typeIds: [Id.Id('362c1dbd-dc64-44bb-a3c4-652f38a642d7')],
properties: {
name: Id.Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
description: Id.Id('9b1f76ff-9711-404c-861e-59dc3fa7d037'),
},
},
Activity: {
typeIds: [Id.Id('8275c359-4662-40fb-9aec-27177b520cd2')],
properties: {
name: Id.Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
description: Id.Id('9b1f76ff-9711-404c-861e-59dc3fa7d037'),
},
relations: {
relatedSpaces: Id.Id('5b722cd3-61d6-494e-8887-1310566437ba'),
},
},
};`;

const mapping = buildMappingFile({
name: 'test',
description: 'test',
directory: 'test',
template: 'vite_react',
types: [
{
name: 'Space',
knowledgeGraphId: '362c1dbd-dc64-44bb-a3c4-652f38a642d7',
properties: [
{
name: 'Name',
knowledgeGraphId: 'a126ca53-0c8e-48d5-b888-82c734c38935',
dataType: 'Text',
},
{
name: 'Description',
knowledgeGraphId: '9b1f76ff-9711-404c-861e-59dc3fa7d037',
dataType: 'Text',
},
],
},
{
name: 'Activity',
knowledgeGraphId: '8275c359-4662-40fb-9aec-27177b520cd2',
properties: [
{
name: 'Name',
knowledgeGraphId: 'a126ca53-0c8e-48d5-b888-82c734c38935',
dataType: 'Text',
},
{
name: 'Description',
knowledgeGraphId: '9b1f76ff-9711-404c-861e-59dc3fa7d037',
dataType: 'Text',
},
{
name: 'Related spaces',
knowledgeGraphId: '5b722cd3-61d6-494e-8887-1310566437ba',
dataType: 'Relation(Related spaces)',
relationType: 'Related spaces',
},
],
},
],
});
expect(mapping).toBe(expectedMapping);
});
});
Loading