mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-07-27 02:32:07 +00:00
Generate relations and entity types maps in indexer (#88)
This commit is contained in:
parent
d96cc095bb
commit
18f73e57c0
@ -11,6 +11,7 @@ import { Writable } from 'stream';
|
|||||||
|
|
||||||
import { getTsForSol, getPgForTs, getTsForGql } from './utils/type-mappings';
|
import { getTsForSol, getPgForTs, getTsForGql } from './utils/type-mappings';
|
||||||
import { Param } from './utils/types';
|
import { Param } from './utils/types';
|
||||||
|
import { getFieldType } from './utils/subgraph';
|
||||||
|
|
||||||
const TEMPLATE_FILE = './templates/entity-template.handlebars';
|
const TEMPLATE_FILE = './templates/entity-template.handlebars';
|
||||||
const TABLES_DIR = './data/entities';
|
const TABLES_DIR = './data/entities';
|
||||||
@ -334,7 +335,7 @@ export class Entity {
|
|||||||
columnType: 'Column'
|
columnType: 'Column'
|
||||||
};
|
};
|
||||||
|
|
||||||
const { typeName, array, nullable } = this._getFieldType(field.type);
|
const { typeName, array, nullable } = getFieldType(field.type);
|
||||||
let tsType = getTsForGql(typeName);
|
let tsType = getTsForGql(typeName);
|
||||||
|
|
||||||
if (!tsType) {
|
if (!tsType) {
|
||||||
@ -396,19 +397,4 @@ export class Entity {
|
|||||||
|
|
||||||
return entityObject;
|
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 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,20 @@ import _ from 'lodash';
|
|||||||
import { getTsForSol } from './utils/type-mappings';
|
import { getTsForSol } from './utils/type-mappings';
|
||||||
import { Param } from './utils/types';
|
import { Param } from './utils/types';
|
||||||
import { MODE_ETH_CALL, MODE_STORAGE } from './utils/constants';
|
import { MODE_ETH_CALL, MODE_STORAGE } from './utils/constants';
|
||||||
|
import { getFieldType } from './utils/subgraph';
|
||||||
|
|
||||||
const TEMPLATE_FILE = './templates/indexer-template.handlebars';
|
const TEMPLATE_FILE = './templates/indexer-template.handlebars';
|
||||||
|
|
||||||
export class Indexer {
|
export class Indexer {
|
||||||
_queries: Array<any>;
|
_queries: Array<any>;
|
||||||
_events: Array<any>;
|
_events: Array<any>;
|
||||||
|
_subgraphEntities: Array<any>;
|
||||||
_templateString: string;
|
_templateString: string;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this._queries = [];
|
this._queries = [];
|
||||||
this._events = [];
|
this._events = [];
|
||||||
|
this._subgraphEntities = [];
|
||||||
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +102,70 @@ export class Indexer {
|
|||||||
this._events.push(eventObject);
|
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.
|
* Writes the indexer file generated from a template to a stream.
|
||||||
* @param outStream A writable output stream to write the indexer file to.
|
* @param outStream A writable output stream to write the indexer file to.
|
||||||
@ -110,6 +177,7 @@ export class Indexer {
|
|||||||
const obj = {
|
const obj = {
|
||||||
inputFileNames,
|
inputFileNames,
|
||||||
queries: this._queries,
|
queries: this._queries,
|
||||||
|
subgraphEntities: this._subgraphEntities,
|
||||||
constants: {
|
constants: {
|
||||||
MODE_ETH_CALL,
|
MODE_ETH_CALL,
|
||||||
MODE_STORAGE
|
MODE_STORAGE
|
||||||
@ -120,4 +188,21 @@ export class Indexer {
|
|||||||
const indexer = template(obj);
|
const indexer = template(obj);
|
||||||
outStream.write(indexer);
|
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 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,10 @@ import { HookStatus } from './entity/HookStatus';
|
|||||||
import { BlockProgress } from './entity/BlockProgress';
|
import { BlockProgress } from './entity/BlockProgress';
|
||||||
import { IPLDBlock } from './entity/IPLDBlock';
|
import { IPLDBlock } from './entity/IPLDBlock';
|
||||||
|
|
||||||
|
{{#each subgraphEntities as | subgraphEntity |}}
|
||||||
|
import { {{subgraphEntity.className}} } from './entity/{{subgraphEntity.className}}';
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
const log = debug('vulcanize:indexer');
|
const log = debug('vulcanize:indexer');
|
||||||
|
|
||||||
{{#each events as | event |}}
|
{{#each events as | event |}}
|
||||||
@ -129,7 +133,10 @@ export class Indexer implements IndexerInterface {
|
|||||||
this._contract = new ethers.utils.Interface(this._abi);
|
this._contract = new ethers.utils.Interface(this._abi);
|
||||||
|
|
||||||
this._entityTypesMap = new Map();
|
this._entityTypesMap = new Map();
|
||||||
|
this._populateEntityTypesMap();
|
||||||
|
|
||||||
this._relationsMap = new Map();
|
this._relationsMap = new Map();
|
||||||
|
this._populateRelationsMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init (): Promise<void> {
|
async init (): Promise<void> {
|
||||||
@ -563,6 +570,48 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEntityTypesMap (): Map<string, { [key: string]: string }> {
|
||||||
|
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<BlockProgress>): Promise<BlockProgress> {
|
async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
assert(blockHash);
|
assert(blockHash);
|
||||||
|
|
||||||
|
@ -20,9 +20,6 @@ export function parseSubgraphSchema (subgraphPath: string): any {
|
|||||||
const subgraphTypeDefs = subgraphSchemaDocument.definitions;
|
const subgraphTypeDefs = subgraphSchemaDocument.definitions;
|
||||||
|
|
||||||
subgraphTypeDefs.forEach((def: any) => {
|
subgraphTypeDefs.forEach((def: any) => {
|
||||||
// Remove type directives.
|
|
||||||
def.directives = [];
|
|
||||||
|
|
||||||
if (def.kind === 'ObjectTypeDefinition') {
|
if (def.kind === 'ObjectTypeDefinition') {
|
||||||
def.fields.forEach((field: any) => {
|
def.fields.forEach((field: any) => {
|
||||||
// Parse the field type.
|
// Parse the field type.
|
||||||
@ -37,6 +34,21 @@ export function parseSubgraphSchema (subgraphPath: string): any {
|
|||||||
return subgraphSchemaDocument;
|
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 {
|
function parseType (typeNode: any): any {
|
||||||
// Check if 'NamedType' is reached.
|
// Check if 'NamedType' is reached.
|
||||||
if (typeNode.kind === 'NamedType') {
|
if (typeNode.kind === 'NamedType') {
|
||||||
|
@ -15,6 +15,7 @@ _solToTs.set('uint16', 'number');
|
|||||||
_solToTs.set('uint64', 'bigint');
|
_solToTs.set('uint64', 'bigint');
|
||||||
_solToTs.set('uint128', 'bigint');
|
_solToTs.set('uint128', 'bigint');
|
||||||
_solToTs.set('uint256', 'bigint');
|
_solToTs.set('uint256', 'bigint');
|
||||||
|
_solToTs.set('uint', 'bigint');
|
||||||
_solToTs.set('address', 'string');
|
_solToTs.set('address', 'string');
|
||||||
_solToTs.set('bool', 'boolean');
|
_solToTs.set('bool', 'boolean');
|
||||||
_solToTs.set('bytes', 'string');
|
_solToTs.set('bytes', 'string');
|
||||||
|
@ -76,6 +76,7 @@ export class Visitor {
|
|||||||
stateVariableDeclarationVisitor (node: any): void {
|
stateVariableDeclarationVisitor (node: any): void {
|
||||||
// TODO Handle multiples variables in a single line.
|
// TODO Handle multiples variables in a single line.
|
||||||
// TODO Handle array types.
|
// TODO Handle array types.
|
||||||
|
// TODO Handle user defined type .
|
||||||
const variable = node.variables[0];
|
const variable = node.variables[0];
|
||||||
const name: string = variable.name;
|
const name: string = variable.name;
|
||||||
const stateVariableType: string = variable.typeName.type;
|
const stateVariableType: string = variable.typeName.type;
|
||||||
@ -83,13 +84,6 @@ export class Visitor {
|
|||||||
const params: Param[] = [];
|
const params: Param[] = [];
|
||||||
|
|
||||||
let typeName = variable.typeName;
|
let typeName = variable.typeName;
|
||||||
|
|
||||||
// TODO Handle user defined type.
|
|
||||||
if (typeName.type === 'UserDefinedTypeName') {
|
|
||||||
// Skip in case of UserDefinedTypeName.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let numParams = 0;
|
let numParams = 0;
|
||||||
|
|
||||||
// If the variable type is mapping, extract key as a param:
|
// If the variable type is mapping, extract key as a param:
|
||||||
@ -100,6 +94,11 @@ export class Visitor {
|
|||||||
numParams++;
|
numParams++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (['UserDefinedTypeName', 'ArrayTypeName'].includes(typeName.type)) {
|
||||||
|
// Skip in case of UserDefinedTypeName | ArrayTypeName.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const returnType = typeName.name;
|
const returnType = typeName.name;
|
||||||
|
|
||||||
this._schema.addQuery(name, params, returnType);
|
this._schema.addQuery(name, params, returnType);
|
||||||
@ -138,6 +137,7 @@ export class Visitor {
|
|||||||
this._entity.addSubgraphEntities(subgraphSchemaDocument);
|
this._entity.addSubgraphEntities(subgraphSchemaDocument);
|
||||||
this._resolvers.addSubgraphResolvers(subgraphSchemaDocument);
|
this._resolvers.addSubgraphResolvers(subgraphSchemaDocument);
|
||||||
this._reset.addSubgraphEntities(subgraphSchemaDocument);
|
this._reset.addSubgraphEntities(subgraphSchemaDocument);
|
||||||
|
this._indexer.addSubgraphEntities(subgraphSchemaDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user