diff --git a/packages/codegen/src/entity.ts b/packages/codegen/src/entity.ts index 7babcb3db..375692aa8 100644 --- a/packages/codegen/src/entity.ts +++ b/packages/codegen/src/entity.ts @@ -11,6 +11,7 @@ import { Writable } from 'stream'; import { getTsForSol, getPgForTs, getTsForGql } from './utils/type-mappings'; import { Param } from './utils/types'; +import { getFieldType } from './utils/subgraph'; const TEMPLATE_FILE = './templates/entity-template.handlebars'; const TABLES_DIR = './data/entities'; @@ -334,7 +335,7 @@ export class Entity { columnType: 'Column' }; - const { typeName, array, nullable } = this._getFieldType(field.type); + const { typeName, array, nullable } = getFieldType(field.type); let tsType = getTsForGql(typeName); if (!tsType) { @@ -396,19 +397,4 @@ export class Entity { return entityObject; } - - _getFieldType (typeNode: any): { typeName: string, array: boolean, nullable: boolean } { - if (typeNode.kind === 'ListType') { - return { typeName: this._getFieldType(typeNode.type).typeName, array: true, nullable: true }; - } - - if (typeNode.kind === 'NonNullType') { - const fieldType = this._getFieldType(typeNode.type); - - return { typeName: fieldType.typeName, array: fieldType.array, nullable: false }; - } - - // If 'NamedType'. - return { typeName: typeNode.name.value, array: false, nullable: true }; - } } diff --git a/packages/codegen/src/indexer.ts b/packages/codegen/src/indexer.ts index 75b4f3736..cd21eb78f 100644 --- a/packages/codegen/src/indexer.ts +++ b/packages/codegen/src/indexer.ts @@ -12,17 +12,20 @@ import _ from 'lodash'; import { getTsForSol } from './utils/type-mappings'; import { Param } from './utils/types'; import { MODE_ETH_CALL, MODE_STORAGE } from './utils/constants'; +import { getFieldType } from './utils/subgraph'; const TEMPLATE_FILE = './templates/indexer-template.handlebars'; export class Indexer { _queries: Array; _events: Array; + _subgraphEntities: Array; _templateString: string; constructor () { this._queries = []; this._events = []; + this._subgraphEntities = []; this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString(); } @@ -99,6 +102,70 @@ export class Indexer { this._events.push(eventObject); } + addSubgraphEntities (subgraphSchemaDocument: any): void { + // Add subgraph entities for creating the relations and entity types maps in the indexer. + const subgraphTypeDefs = subgraphSchemaDocument.definitions; + + subgraphTypeDefs.forEach((def: any) => { + if (def.kind !== 'ObjectTypeDefinition') { + return; + } + + let entityObject: any = { + className: def.name.value, + columns: [], + relations: [] + }; + + entityObject = this._addSubgraphColumns(subgraphTypeDefs, entityObject, def); + + this._subgraphEntities.push(entityObject); + }); + } + + _addSubgraphColumns (subgraphTypeDefs: any, entityObject: any, def: any): any { + // Process each field of the entity type def. + def.fields.forEach((field: any) => { + const columnObject: any = { + name: field.name.value, + type: '', + isRelation: false, + isArray: false + }; + + // Process field properties. + const { typeName, array } = getFieldType(field.type); + + columnObject.type = typeName; + columnObject.isArray = array; + + // Add a relation if the type is a object type available in subgraph type defs. + const columnType = subgraphTypeDefs.find((def: any) => { + return def.name.value === typeName && def.kind === 'ObjectTypeDefinition'; + }); + + if (columnType) { + columnObject.isRelation = true; + + // Process the derivedFrom directive for the relation field. + const { isDerived, derivedFromField } = this._getDerivedFrom(field.directives); + + if (isDerived) { + columnObject.isDerived = true; + columnObject.derivedFromField = derivedFromField; + } else { + columnObject.isDerived = false; + } + + entityObject.relations.push(columnObject); + } + + entityObject.columns.push(columnObject); + }); + + return entityObject; + } + /** * Writes the indexer file generated from a template to a stream. * @param outStream A writable output stream to write the indexer file to. @@ -110,6 +177,7 @@ export class Indexer { const obj = { inputFileNames, queries: this._queries, + subgraphEntities: this._subgraphEntities, constants: { MODE_ETH_CALL, MODE_STORAGE @@ -120,4 +188,21 @@ export class Indexer { const indexer = template(obj); outStream.write(indexer); } + + _getDerivedFrom (directives: any): { isDerived: boolean, derivedFromField: string } { + const derivedFromDirective = directives.find((directive: any) => { + return directive.name.value === 'derivedFrom'; + }); + + let isDerived = false; + let derivedFromField = ''; + + // Get the derivedFrom field name if derivedFrom directive is present. + if (derivedFromDirective) { + isDerived = true; + derivedFromField = derivedFromDirective.arguments[0].value.value; + } + + return { isDerived, derivedFromField }; + } } diff --git a/packages/codegen/src/templates/indexer-template.handlebars b/packages/codegen/src/templates/indexer-template.handlebars index 2405176e3..36cdd61f4 100644 --- a/packages/codegen/src/templates/indexer-template.handlebars +++ b/packages/codegen/src/templates/indexer-template.handlebars @@ -42,6 +42,10 @@ import { HookStatus } from './entity/HookStatus'; import { BlockProgress } from './entity/BlockProgress'; import { IPLDBlock } from './entity/IPLDBlock'; +{{#each subgraphEntities as | subgraphEntity |}} +import { {{subgraphEntity.className}} } from './entity/{{subgraphEntity.className}}'; +{{/each}} + const log = debug('vulcanize:indexer'); {{#each events as | event |}} @@ -129,7 +133,10 @@ export class Indexer implements IndexerInterface { this._contract = new ethers.utils.Interface(this._abi); this._entityTypesMap = new Map(); + this._populateEntityTypesMap(); + this._relationsMap = new Map(); + this._populateRelationsMap(); } async init (): Promise { @@ -563,6 +570,48 @@ export class Indexer implements IndexerInterface { return this._baseIndexer.getAncestorAtDepth(blockHash, depth); } + getEntityTypesMap (): Map { + return this._entityTypesMap; + } + + _populateEntityTypesMap (): void { + {{#each subgraphEntities as | subgraphEntity |}} + this._entityTypesMap.set('{{subgraphEntity.className}}', { + {{#each subgraphEntity.columns as | column |}} + {{#unless column.isDerived}} + {{~#unless @first}}, + {{/unless}} + {{column.name}}: '{{column.type}}' + {{~/unless}} + {{/each}} + + }); + {{/each}} + } + + _populateRelationsMap (): void { + {{#each subgraphEntities as | subgraphEntity |}} + {{#if subgraphEntity.relations}} + this._relationsMap.set({{subgraphEntity.className}}, { + {{#each subgraphEntity.relations as | relation |}} + {{~#unless @first}}, + {{/unless}} + {{relation.name}}: { + entity: {{relation.type}}, + isArray: {{relation.isArray}}, + isDerived: {{relation.isDerived}} + {{~#if relation.isDerived}}, + field: '{{relation.derivedFromField}}' + {{~/if}} + + } + {{~/each}} + + }); + {{/if}} + {{/each}} + } + async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial): Promise { assert(blockHash); diff --git a/packages/codegen/src/utils/subgraph.ts b/packages/codegen/src/utils/subgraph.ts index 6c69a6173..19135355a 100644 --- a/packages/codegen/src/utils/subgraph.ts +++ b/packages/codegen/src/utils/subgraph.ts @@ -20,9 +20,6 @@ export function parseSubgraphSchema (subgraphPath: string): any { const subgraphTypeDefs = subgraphSchemaDocument.definitions; subgraphTypeDefs.forEach((def: any) => { - // Remove type directives. - def.directives = []; - if (def.kind === 'ObjectTypeDefinition') { def.fields.forEach((field: any) => { // Parse the field type. @@ -37,6 +34,21 @@ export function parseSubgraphSchema (subgraphPath: string): any { return subgraphSchemaDocument; } +export function getFieldType (typeNode: any): { typeName: string, array: boolean, nullable: boolean } { + if (typeNode.kind === 'ListType') { + return { typeName: getFieldType(typeNode.type).typeName, array: true, nullable: true }; + } + + if (typeNode.kind === 'NonNullType') { + const fieldType = getFieldType(typeNode.type); + + return { typeName: fieldType.typeName, array: fieldType.array, nullable: false }; + } + + // If 'NamedType'. + return { typeName: typeNode.name.value, array: false, nullable: true }; +} + function parseType (typeNode: any): any { // Check if 'NamedType' is reached. if (typeNode.kind === 'NamedType') { diff --git a/packages/codegen/src/utils/type-mappings.ts b/packages/codegen/src/utils/type-mappings.ts index 06926a746..b1bf20409 100644 --- a/packages/codegen/src/utils/type-mappings.ts +++ b/packages/codegen/src/utils/type-mappings.ts @@ -15,6 +15,7 @@ _solToTs.set('uint16', 'number'); _solToTs.set('uint64', 'bigint'); _solToTs.set('uint128', 'bigint'); _solToTs.set('uint256', 'bigint'); +_solToTs.set('uint', 'bigint'); _solToTs.set('address', 'string'); _solToTs.set('bool', 'boolean'); _solToTs.set('bytes', 'string'); diff --git a/packages/codegen/src/visitor.ts b/packages/codegen/src/visitor.ts index 502203c09..8e486493b 100644 --- a/packages/codegen/src/visitor.ts +++ b/packages/codegen/src/visitor.ts @@ -76,6 +76,7 @@ export class Visitor { stateVariableDeclarationVisitor (node: any): void { // TODO Handle multiples variables in a single line. // TODO Handle array types. + // TODO Handle user defined type . const variable = node.variables[0]; const name: string = variable.name; const stateVariableType: string = variable.typeName.type; @@ -83,13 +84,6 @@ export class Visitor { const params: Param[] = []; let typeName = variable.typeName; - - // TODO Handle user defined type. - if (typeName.type === 'UserDefinedTypeName') { - // Skip in case of UserDefinedTypeName. - return; - } - let numParams = 0; // If the variable type is mapping, extract key as a param: @@ -100,6 +94,11 @@ export class Visitor { numParams++; } + if (['UserDefinedTypeName', 'ArrayTypeName'].includes(typeName.type)) { + // Skip in case of UserDefinedTypeName | ArrayTypeName. + return; + } + const returnType = typeName.name; this._schema.addQuery(name, params, returnType); @@ -138,6 +137,7 @@ export class Visitor { this._entity.addSubgraphEntities(subgraphSchemaDocument); this._resolvers.addSubgraphResolvers(subgraphSchemaDocument); this._reset.addSubgraphEntities(subgraphSchemaDocument); + this._indexer.addSubgraphEntities(subgraphSchemaDocument); } /**