Skip to content

Commit 7a2f13a

Browse files
authored
typesync improvements (#295)
1 parent b782faf commit 7a2f13a

File tree

4 files changed

+138
-19
lines changed

4 files changed

+138
-19
lines changed

apps/typesync/client/src/Components/FormComponents/Input.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export function FormComponentTextField({ id, label, hint, ...rest }: Readonly<Fo
3333
{...rest}
3434
value={field.state.value}
3535
onBlur={field.handleBlur}
36-
onChange={(e) => field.handleChange(e.target.value)}
36+
onChange={(e) => {
37+
field.handleChange(e.target.value);
38+
rest.onChange?.(e);
39+
}}
3740
data-state={hasErrors ? 'invalid' : undefined}
3841
aria-invalid={hasErrors ? 'true' : undefined}
3942
aria-describedby={hasErrors ? `${id}-invalid` : hint != null ? `${id}-hint` : undefined}

apps/typesync/client/src/routes/apps/create.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -366,30 +366,20 @@ function CreateAppPage() {
366366
<h2 className="text-base/7 font-semibold text-gray-900 dark:text-white">Create New App</h2>
367367
<div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
368368
<div className="sm:col-span-4">
369-
<createAppForm.AppField
370-
name="name"
371-
listeners={{
372-
onBlur({ value }) {
373-
// set the default value of the directory to be `./${formatted app name}
374-
if (
375-
EffectString.isNonEmpty(value) &&
376-
EffectString.isEmpty(createAppForm.state.values.directory || '')
377-
) {
378-
createAppForm.setFieldValue(
379-
'directory',
380-
`./${pipe(value, EffectString.toLowerCase, EffectString.replaceAll(/\s/g, '-'))}`,
381-
);
382-
}
383-
},
384-
}}
385-
>
369+
<createAppForm.AppField name="name">
386370
{(field) => (
387371
<field.FormComponentTextField
388372
id="name"
389373
name="name"
390374
required
391375
label="App name"
392376
hint="App name must be unique"
377+
onChange={(e) => {
378+
createAppForm.setFieldValue(
379+
'directory',
380+
`./${pipe(e.target.value, EffectString.toLowerCase, EffectString.replaceAll(/\s/g, '-'))}`,
381+
);
382+
}}
393383
/>
394384
)}
395385
</createAppForm.AppField>
@@ -808,7 +798,7 @@ function CreateAppPage() {
808798
</div>
809799
</TabsPrimitive.Content>
810800

811-
<TabsPrimitive.List className="mt-6 flex items-center justify-end gap-x-6">
801+
<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">
812802
<Link to="/" className="text-sm/6 font-semibold text-gray-900 dark:text-white">
813803
Cancel
814804
</Link>

apps/typesync/src/Generator.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export class SchemaGenerator extends Effect.Service<SchemaGenerator>()('/typesyn
117117
),
118118
Effect.andThen(() => updatePackageJson(app, directory)),
119119
Effect.andThen(() => fs.writeFileString(path.join(directory, 'src', 'schema.ts'), buildSchemaFile(app))),
120+
Effect.andThen(() => fs.writeFileString(path.join(directory, 'src', 'mapping.ts'), buildMappingFile(app))),
120121
Effect.andThen(() => cleanup),
121122
);
122123

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

291292
function buildSchemaFile(schema: Domain.InsertAppSchema) {
292293
const importStatement = `import { Entity, Type } from '@graphprotocol/hypergraph';`;
294+
293295
const typeDefinitions = schema.types
294296
.map(typeDefinitionToString)
295297
.filter((def) => def != null)
296298
.join('\n\n');
297299
return [importStatement, typeDefinitions].join('\n\n');
298300
}
301+
302+
export function buildMappingFile(schema: Domain.InsertAppSchema) {
303+
const importStatement1 = `import { Id } from '@graphprotocol/grc-20';`;
304+
const importStatement2 = `import type { Mapping } from '@graphprotocol/hypergraph';`;
305+
306+
const typeMappings: string[] = [];
307+
308+
for (const type of schema.types) {
309+
const properties: string[] = [];
310+
const relations: string[] = [];
311+
312+
// Process properties and relations
313+
for (const property of type.properties) {
314+
if (Domain.isDataTypeRelation(property.dataType)) {
315+
// This is a relation
316+
relations.push(` ${Utils.toCamelCase(property.name)}: Id.Id('${property.knowledgeGraphId}')`);
317+
} else {
318+
// This is a regular property
319+
properties.push(` ${Utils.toCamelCase(property.name)}: Id.Id('${property.knowledgeGraphId}')`);
320+
}
321+
}
322+
323+
const typeMapping = ` ${type.name}: {
324+
typeIds: [Id.Id('${type.knowledgeGraphId}')],
325+
properties: {
326+
${properties.join(',\n')},
327+
},${
328+
relations.length > 0
329+
? `
330+
relations: {
331+
${relations.join(',\n')},
332+
},`
333+
: ''
334+
}
335+
}`;
336+
337+
typeMappings.push(typeMapping);
338+
}
339+
340+
const mappingString = `export const mapping: Mapping = {
341+
${typeMappings.join(',\n')},
342+
};`;
343+
344+
return [importStatement1, importStatement2, '', mappingString].join('\n');
345+
}

apps/typesync/test/Generator.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { describe, expect, it } from '@effect/vitest';
2+
3+
// @ts-ignore - fix the ts setup
4+
import { buildMappingFile } from '../src/Generator.js';
5+
6+
describe('buildMappingFile', () => {
7+
it('should build a valid mapping file', () => {
8+
const expectedMapping = `import { Id } from '@graphprotocol/grc-20';
9+
import type { Mapping } from '@graphprotocol/hypergraph';
10+
11+
export const mapping: Mapping = {
12+
Space: {
13+
typeIds: [Id.Id('362c1dbd-dc64-44bb-a3c4-652f38a642d7')],
14+
properties: {
15+
name: Id.Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
16+
description: Id.Id('9b1f76ff-9711-404c-861e-59dc3fa7d037'),
17+
},
18+
},
19+
Activity: {
20+
typeIds: [Id.Id('8275c359-4662-40fb-9aec-27177b520cd2')],
21+
properties: {
22+
name: Id.Id('a126ca53-0c8e-48d5-b888-82c734c38935'),
23+
description: Id.Id('9b1f76ff-9711-404c-861e-59dc3fa7d037'),
24+
},
25+
relations: {
26+
relatedSpaces: Id.Id('5b722cd3-61d6-494e-8887-1310566437ba'),
27+
},
28+
},
29+
};`;
30+
31+
const mapping = buildMappingFile({
32+
name: 'test',
33+
description: 'test',
34+
directory: 'test',
35+
template: 'vite_react',
36+
types: [
37+
{
38+
name: 'Space',
39+
knowledgeGraphId: '362c1dbd-dc64-44bb-a3c4-652f38a642d7',
40+
properties: [
41+
{
42+
name: 'Name',
43+
knowledgeGraphId: 'a126ca53-0c8e-48d5-b888-82c734c38935',
44+
dataType: 'Text',
45+
},
46+
{
47+
name: 'Description',
48+
knowledgeGraphId: '9b1f76ff-9711-404c-861e-59dc3fa7d037',
49+
dataType: 'Text',
50+
},
51+
],
52+
},
53+
{
54+
name: 'Activity',
55+
knowledgeGraphId: '8275c359-4662-40fb-9aec-27177b520cd2',
56+
properties: [
57+
{
58+
name: 'Name',
59+
knowledgeGraphId: 'a126ca53-0c8e-48d5-b888-82c734c38935',
60+
dataType: 'Text',
61+
},
62+
{
63+
name: 'Description',
64+
knowledgeGraphId: '9b1f76ff-9711-404c-861e-59dc3fa7d037',
65+
dataType: 'Text',
66+
},
67+
{
68+
name: 'Related spaces',
69+
knowledgeGraphId: '5b722cd3-61d6-494e-8887-1310566437ba',
70+
dataType: 'Relation(Related spaces)',
71+
relationType: 'Related spaces',
72+
},
73+
],
74+
},
75+
],
76+
});
77+
expect(mapping).toBe(expectedMapping);
78+
});
79+
});

0 commit comments

Comments
 (0)