Skip to content

Commit 55f79ae

Browse files
committed
feat(typesync pkg): build initial generateMapping fn
1 parent 2cc06ee commit 55f79ae

File tree

4 files changed

+246
-7
lines changed

4 files changed

+246
-7
lines changed

.DS_Store

0 Bytes
Binary file not shown.

packages/typesync/src/Mapping.ts

Lines changed: 163 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { Id as Grc20Id } from '@graphprotocol/grc-20';
1+
import { type CreatePropertyParams, Graph, Id as Grc20Id, type Op } from '@graphprotocol/grc-20';
2+
import { Array as EffectArray, Schema as EffectSchema, pipe } from 'effect';
23

34
/**
45
* Mappings for a schema type and its properties/relations
@@ -18,17 +19,21 @@ export type MappingEntry = {
1819
*
1920
* @since 0.0.1
2021
*/
21-
properties?: {
22-
[key: string]: Grc20Id.Id;
23-
};
22+
properties?:
23+
| {
24+
[key: string]: Grc20Id.Id;
25+
}
26+
| undefined;
2427
/**
2528
* Record of schema type relation names to the `Id.Id` of the relation in the Knowledge Graph
2629
*
2730
* @since 0.0.1
2831
*/
29-
relations?: {
30-
[key: string]: Grc20Id.Id;
31-
};
32+
relations?:
33+
| {
34+
[key: string]: Grc20Id.Id;
35+
}
36+
| undefined;
3237
};
3338

3439
/**
@@ -63,3 +68,154 @@ export type MappingEntry = {
6368
export type Mapping = {
6469
[key: string]: MappingEntry;
6570
};
71+
72+
export type DataTypeRelation = `Relation(${string})`;
73+
export function isDataTypeRelation(val: string): val is DataTypeRelation {
74+
return /^Relation\((.+)\)$/.test(val);
75+
}
76+
export const SchemaDataTypeRelation = EffectSchema.NonEmptyTrimmedString.pipe(
77+
EffectSchema.filter((val) => isDataTypeRelation(val)),
78+
);
79+
export type SchemaDataTypeRelation = typeof SchemaDataTypeRelation.Type;
80+
81+
export const SchemaDataType = EffectSchema.Union(
82+
EffectSchema.Literal('Text', 'Number', 'Boolean', 'Date', 'Point', 'Url'),
83+
SchemaDataTypeRelation,
84+
);
85+
export type SchemaDataType = typeof SchemaDataType.Type;
86+
87+
export const Schema = EffectSchema.Struct({
88+
types: EffectSchema.Array(
89+
EffectSchema.Struct({
90+
name: EffectSchema.NonEmptyTrimmedString,
91+
knowledgeGraphId: EffectSchema.NullOr(EffectSchema.UUID),
92+
properties: EffectSchema.Array(
93+
EffectSchema.Struct({
94+
name: EffectSchema.NonEmptyTrimmedString,
95+
knowledgeGraphId: EffectSchema.NullOr(EffectSchema.UUID),
96+
dataType: SchemaDataType,
97+
}),
98+
).pipe(EffectSchema.minItems(1)),
99+
}),
100+
).pipe(EffectSchema.minItems(1)),
101+
}).annotations({
102+
identifier: 'typesync/Schema',
103+
title: 'TypeSync app Schema',
104+
description: 'An array of types in the schema defined by the user to generate a Mapping object for',
105+
examples: [
106+
{
107+
types: [
108+
{
109+
name: 'Account',
110+
knowledgeGraphId: null,
111+
properties: [{ name: 'username', knowledgeGraphId: null, dataType: 'Text' }],
112+
},
113+
],
114+
},
115+
{
116+
types: [
117+
{
118+
name: 'Account',
119+
knowledgeGraphId: 'a5fd07b1-120f-46c6-b46f-387ef98396a6',
120+
properties: [{ name: 'name', knowledgeGraphId: 'a126ca53-0c8e-48d5-b888-82c734c38935', dataType: 'Text' }],
121+
},
122+
],
123+
},
124+
],
125+
});
126+
export type Schema = typeof Schema.Type;
127+
128+
export async function generateMapping(schema: Schema): Promise<Mapping> {
129+
const entries: Array<MappingEntry & { typeName: string }> = [];
130+
const ops: Array<Op> = [];
131+
132+
for (const type of schema.types) {
133+
const typePropertyIds: Array<{ propName: string; id: Grc20Id.Id }> = [];
134+
for (const property of type.properties) {
135+
if (property.knowledgeGraphId) {
136+
typePropertyIds.push({ propName: property.name, id: Grc20Id.Id(property.knowledgeGraphId) });
137+
continue;
138+
}
139+
// create op for creating type property
140+
const { id, ops: createTypePropOp } = Graph.createProperty({
141+
name: property.name,
142+
dataType: mapSchemaDataTypeToGRC20PropDataType(property.dataType),
143+
});
144+
typePropertyIds.push({ propName: property.name, id });
145+
// add createProperty ops to array to submit in batch to KG
146+
ops.push(...createTypePropOp);
147+
}
148+
149+
const properties: MappingEntry['properties'] = pipe(
150+
typePropertyIds,
151+
EffectArray.reduce({} as NonNullable<MappingEntry['properties']>, (props, { propName, id }) => {
152+
props[propName] = id;
153+
154+
return props;
155+
}),
156+
);
157+
158+
const relations: MappingEntry['relations'] = undefined;
159+
160+
if (type.knowledgeGraphId) {
161+
entries.push({
162+
typeName: type.name,
163+
typeIds: [Grc20Id.Id(type.knowledgeGraphId)],
164+
properties,
165+
relations,
166+
});
167+
continue;
168+
}
169+
// create the type op, with its properties
170+
const { id, ops: createTypeOp } = Graph.createType({
171+
name: type.name,
172+
properties: EffectArray.map(typePropertyIds, ({ id }) => id),
173+
});
174+
ops.push(...createTypeOp);
175+
176+
entries.push({
177+
typeName: type.name,
178+
typeIds: [id],
179+
properties,
180+
relations,
181+
});
182+
}
183+
184+
// @todo send ops to Knowledge Graph
185+
186+
return pipe(
187+
entries,
188+
EffectArray.reduce({} as Mapping, (mapping, entry) => {
189+
const { typeName, ...rest } = entry;
190+
mapping[typeName] = rest;
191+
192+
return mapping;
193+
}),
194+
);
195+
}
196+
197+
export function mapSchemaDataTypeToGRC20PropDataType(dataType: SchemaDataType): CreatePropertyParams['dataType'] {
198+
switch (true) {
199+
case dataType === 'Boolean': {
200+
return 'CHECKBOX';
201+
}
202+
case dataType === 'Date': {
203+
return 'TIME';
204+
}
205+
case dataType === 'Number': {
206+
return 'NUMBER';
207+
}
208+
case dataType === 'Point': {
209+
return 'POINT';
210+
}
211+
case dataType === 'Url': {
212+
return 'TEXT';
213+
}
214+
case isDataTypeRelation(dataType): {
215+
return 'RELATION';
216+
}
217+
default: {
218+
return 'TEXT';
219+
}
220+
}
221+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { type Mapping, generateMapping, mapSchemaDataTypeToGRC20PropDataType } from '../src/Mapping.js';
4+
5+
describe('Mapping', () => {
6+
describe('mapSchemaDataTypeToGRC20PropDataType', () => {
7+
it('should be able to map the schema dataType to the correct GRC-20 dataType', () => {
8+
expect(mapSchemaDataTypeToGRC20PropDataType('Boolean')).toEqual('CHECKBOX');
9+
expect(mapSchemaDataTypeToGRC20PropDataType('Number')).toEqual('NUMBER');
10+
expect(mapSchemaDataTypeToGRC20PropDataType('Date')).toEqual('TIME');
11+
expect(mapSchemaDataTypeToGRC20PropDataType('Point')).toEqual('POINT');
12+
expect(mapSchemaDataTypeToGRC20PropDataType('Url')).toEqual('TEXT');
13+
expect(mapSchemaDataTypeToGRC20PropDataType('Text')).toEqual('TEXT');
14+
expect(mapSchemaDataTypeToGRC20PropDataType('Relation(Event)')).toEqual('RELATION');
15+
});
16+
});
17+
18+
describe('generateMapping', () => {
19+
it('should be able to map the input schema to a resulting Mapping definition', async () => {
20+
const actual = await generateMapping({
21+
types: [
22+
{
23+
name: 'Account',
24+
knowledgeGraphId: null,
25+
properties: [
26+
{
27+
name: 'username',
28+
dataType: 'Text',
29+
knowledgeGraphId: null,
30+
},
31+
{
32+
name: 'createdAt',
33+
dataType: 'Date',
34+
knowledgeGraphId: null,
35+
},
36+
],
37+
},
38+
{
39+
name: 'Event',
40+
knowledgeGraphId: null,
41+
properties: [
42+
{
43+
name: 'name',
44+
dataType: 'Text',
45+
knowledgeGraphId: null,
46+
},
47+
{
48+
name: 'description',
49+
dataType: 'Text',
50+
knowledgeGraphId: null,
51+
},
52+
{
53+
name: 'speaker',
54+
dataType: 'Relation(Account)',
55+
knowledgeGraphId: null,
56+
},
57+
],
58+
},
59+
],
60+
});
61+
const expected: Mapping = {
62+
Account: {
63+
typeIds: [expect.any(String)],
64+
properties: {
65+
username: expect.any(String),
66+
createdAt: expect.any(String),
67+
},
68+
},
69+
Event: {
70+
typeIds: [expect.any(String)],
71+
properties: {
72+
name: expect.any(String),
73+
description: expect.any(String),
74+
speaker: expect.any(String),
75+
},
76+
},
77+
};
78+
79+
expect(actual).toEqual(expected);
80+
});
81+
});
82+
});

scripts/package.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const publishPkgJson = {
3232
module: pkgJson.module,
3333
types: pkgJson.types,
3434
sideEffects: pkgJson.sideEffects,
35+
exports: pkgJson.exports,
3536
peerDependencies: pkgJson.peerDependencies,
3637
dependencies: pkgJson.dependencies,
3738
publishConfig: {

0 commit comments

Comments
 (0)