mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Handle subgraph schema field with derivedFrom directive (#60)
* Handle subgraph schema field with derivedFrom directive * Handle derivedFrom directive in eden-watcher * Fix 1 to N relation error by removing limit from query * Order by id for derivedFrom relations to match graph-node * Refactor example subgraph schema entities * Fix watcher queries to return correct relation field values * Fix hierarchical query for getting two entities at same block
This commit is contained in:
parent
b04f6f2fba
commit
238ad21189
@ -2,9 +2,8 @@
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { Claim } from './Claim';
|
||||
import { Slash } from './Slash';
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
|
||||
import { bigintTransformer } from '@vulcanize/util';
|
||||
|
||||
@Entity()
|
||||
@ -23,10 +22,4 @@ export class Account {
|
||||
|
||||
@Column('bigint', { transformer: bigintTransformer })
|
||||
totalSlashed!: bigint;
|
||||
|
||||
@ManyToOne(() => Claim)
|
||||
claims!: Claim;
|
||||
|
||||
@ManyToOne(() => Slash)
|
||||
slashes!: Slash;
|
||||
}
|
||||
|
@ -2,10 +2,9 @@
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
import Decimal from 'decimal.js';
|
||||
|
||||
import { ProducerEpoch } from './ProducerEpoch';
|
||||
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
@Entity()
|
||||
@ -39,7 +38,4 @@ export class Epoch {
|
||||
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
producerBlocksRatio!: Decimal;
|
||||
|
||||
@ManyToOne(() => ProducerEpoch)
|
||||
producerRewards!: ProducerEpoch;
|
||||
}
|
||||
|
@ -2,13 +2,11 @@
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
import Decimal from 'decimal.js';
|
||||
|
||||
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
import { SlotClaim } from './SlotClaim';
|
||||
|
||||
@Entity()
|
||||
export class Slot {
|
||||
@PrimaryColumn('varchar')
|
||||
@ -40,7 +38,4 @@ export class Slot {
|
||||
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
taxRatePerDay!: Decimal;
|
||||
|
||||
@ManyToOne(() => SlotClaim)
|
||||
claims!: SlotClaim;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import { BaseProvider } from '@ethersproject/providers';
|
||||
import * as codec from '@ipld/dag-cbor';
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import { StorageLayout } from '@vulcanize/solidity-mapper';
|
||||
import { EventInterface, Indexer as BaseIndexer, IndexerInterface, UNKNOWN_EVENT_NAME, ServerConfig, JobQueue } from '@vulcanize/util';
|
||||
import { EventInterface, Indexer as BaseIndexer, IndexerInterface, UNKNOWN_EVENT_NAME, ServerConfig, JobQueue, BlockHeight } from '@vulcanize/util';
|
||||
import { GraphWatcher } from '@vulcanize/graph-node';
|
||||
|
||||
import { Database } from './database';
|
||||
@ -549,10 +549,10 @@ export class Indexer implements IndexerInterface {
|
||||
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
|
||||
}
|
||||
|
||||
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash?: string): Promise<any> {
|
||||
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, block?: BlockHeight): Promise<any> {
|
||||
const relations = this._relationsMap.get(entity) || {};
|
||||
|
||||
const data = await this._graphWatcher.getEntity(entity, id, relations, blockHash);
|
||||
const data = await this._graphWatcher.getEntity(entity, id, relations, block);
|
||||
|
||||
return data;
|
||||
}
|
||||
@ -1144,78 +1144,120 @@ export class Indexer implements IndexerInterface {
|
||||
this._relationsMap.set(ProducerSet, {
|
||||
producers: {
|
||||
entity: Producer,
|
||||
isArray: true
|
||||
isArray: true,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(RewardSchedule, {
|
||||
rewardScheduleEntries: {
|
||||
entity: RewardScheduleEntry,
|
||||
isArray: true
|
||||
isArray: true,
|
||||
isDerived: false
|
||||
},
|
||||
activeRewardScheduleEntry: {
|
||||
entity: RewardScheduleEntry,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(ProducerEpoch, {
|
||||
epoch: {
|
||||
entity: Epoch,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Epoch, {
|
||||
startBlock: {
|
||||
entity: Block,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
},
|
||||
endBlock: {
|
||||
entity: Block,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
},
|
||||
producerRewards: {
|
||||
entity: ProducerEpoch,
|
||||
isArray: true,
|
||||
isDerived: true,
|
||||
field: 'epoch'
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(SlotClaim, {
|
||||
slot: {
|
||||
entity: Slot,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Network, {
|
||||
stakers: {
|
||||
entity: Staker,
|
||||
isArray: true
|
||||
isArray: true,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Distributor, {
|
||||
currentDistribution: {
|
||||
entity: Distribution,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Distribution, {
|
||||
distributor: {
|
||||
entity: Distributor,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Claim, {
|
||||
account: {
|
||||
entity: Account,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Slash, {
|
||||
account: {
|
||||
entity: Account,
|
||||
isArray: false
|
||||
isArray: false,
|
||||
isDerived: false
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Slot, {
|
||||
claims: {
|
||||
entity: SlotClaim,
|
||||
isArray: true,
|
||||
isDerived: true,
|
||||
field: 'slot'
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Account, {
|
||||
claims: {
|
||||
entity: Claim,
|
||||
isArray: true,
|
||||
isDerived: true,
|
||||
field: 'account'
|
||||
},
|
||||
slashes: {
|
||||
entity: Slash,
|
||||
isArray: true,
|
||||
isDerived: true,
|
||||
field: 'account'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -65,109 +65,109 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
|
||||
producer: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('producer', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Producer, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Producer, id, block);
|
||||
},
|
||||
|
||||
producerSet: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('producerSet', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(ProducerSet, id, block.hash);
|
||||
return indexer.getSubgraphEntity(ProducerSet, id, block);
|
||||
},
|
||||
|
||||
producerSetChange: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('producerSetChange', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(ProducerSetChange, id, block.hash);
|
||||
return indexer.getSubgraphEntity(ProducerSetChange, id, block);
|
||||
},
|
||||
|
||||
producerRewardCollectorChange: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('producerRewardCollectorChange', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(ProducerRewardCollectorChange, id, block.hash);
|
||||
return indexer.getSubgraphEntity(ProducerRewardCollectorChange, id, block);
|
||||
},
|
||||
|
||||
rewardScheduleEntry: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('rewardScheduleEntry', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(RewardScheduleEntry, id, block.hash);
|
||||
return indexer.getSubgraphEntity(RewardScheduleEntry, id, block);
|
||||
},
|
||||
|
||||
rewardSchedule: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('rewardSchedule', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(RewardSchedule, id, block.hash);
|
||||
return indexer.getSubgraphEntity(RewardSchedule, id, block);
|
||||
},
|
||||
|
||||
producerEpoch: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('producerEpoch', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(ProducerEpoch, id, block.hash);
|
||||
return indexer.getSubgraphEntity(ProducerEpoch, id, block);
|
||||
},
|
||||
|
||||
block: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('block', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Block, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Block, id, block);
|
||||
},
|
||||
|
||||
epoch: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('epoch', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Epoch, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Epoch, id, block);
|
||||
},
|
||||
|
||||
slotClaim: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('slotClaim', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(SlotClaim, id, block.hash);
|
||||
return indexer.getSubgraphEntity(SlotClaim, id, block);
|
||||
},
|
||||
|
||||
slot: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('slot', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Slot, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Slot, id, block);
|
||||
},
|
||||
|
||||
staker: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('staker', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Staker, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Staker, id, block);
|
||||
},
|
||||
|
||||
network: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('network', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Network, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Network, id, block);
|
||||
},
|
||||
|
||||
distributor: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('distributor', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Distributor, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Distributor, id, block);
|
||||
},
|
||||
|
||||
distribution: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('distribution', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Distribution, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Distribution, id, block);
|
||||
},
|
||||
|
||||
claim: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('claim', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Claim, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Claim, id, block);
|
||||
},
|
||||
|
||||
slash: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('slash', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Slash, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Slash, id, block);
|
||||
},
|
||||
|
||||
account: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
|
||||
log('account', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(Account, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Account, id, block);
|
||||
},
|
||||
|
||||
events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => {
|
||||
|
@ -40,7 +40,7 @@
|
||||
## Run
|
||||
|
||||
* Compare query results from two different GQL endpoints:
|
||||
|
||||
|
||||
* In a config file (sample: `environments/compare-cli-config.toml`):
|
||||
|
||||
* Specify the two GQL endpoints in the endpoints config.
|
||||
@ -53,7 +53,7 @@
|
||||
[endpoints]
|
||||
gqlEndpoint1 = "http://localhost:8000/subgraphs/name/example1"
|
||||
gqlEndpoint2 = "http://localhost:3008/graphql"
|
||||
|
||||
|
||||
[queries]
|
||||
queryDir = "../graph-test-watcher/src/gql/queries"
|
||||
```
|
||||
@ -70,11 +70,11 @@
|
||||
* `block-hash`(alias: `b`): Block hash (required).
|
||||
* `entity-id`(alias: `i`): Entity Id (required).
|
||||
* `raw-json`(alias: `j`): Whether to print out a raw diff object (default: `false`).
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
yarn compare-entity --config-file environments/compare-cli-config.toml --query-name exampleEntity --block-hash 0xceed7ee9d3de97c99db12e42433cae9115bb311c516558539fb7114fa17d545b --entity-id 0x2886bae64814bd959aec4282f86f3a97bf1e16e4111b39fd7bdd592b516c66c6
|
||||
yarn compare-entity --config-file environments/compare-cli-config.toml --query-name author --block-hash 0xceed7ee9d3de97c99db12e42433cae9115bb311c516558539fb7114fa17d545b --entity-id 0xdc7d7a8920c8eecc098da5b7522a5f31509b5bfc
|
||||
```
|
||||
|
||||
|
||||
* The program will exit with code `1` if the query results are not equal.
|
||||
|
@ -6,10 +6,12 @@ import assert from 'assert';
|
||||
import {
|
||||
Connection,
|
||||
ConnectionOptions,
|
||||
FindOneOptions
|
||||
FindOneOptions,
|
||||
LessThanOrEqual
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
BlockHeight,
|
||||
Database as BaseDatabase
|
||||
} from '@vulcanize/util';
|
||||
|
||||
@ -41,7 +43,6 @@ export class Database {
|
||||
}
|
||||
|
||||
async getEntity<Entity> (entity: (new () => Entity) | string, id: string, blockHash?: string): Promise<Entity | undefined> {
|
||||
// TODO: Take block number as an optional argument
|
||||
const queryRunner = this._conn.createQueryRunner();
|
||||
|
||||
try {
|
||||
@ -74,63 +75,108 @@ export class Database {
|
||||
}
|
||||
}
|
||||
|
||||
async getEntityWithRelations<Entity> (entity: (new () => Entity) | string, id: string, relations: { [key: string]: any }, blockHash?: string): Promise<Entity | undefined> {
|
||||
async getEntityWithRelations<Entity> (entity: (new () => Entity) | string, id: string, relations: { [key: string]: any }, block: BlockHeight = {}): Promise<Entity | undefined> {
|
||||
const queryRunner = this._conn.createQueryRunner();
|
||||
let { hash: blockHash, number: blockNumber } = block;
|
||||
|
||||
try {
|
||||
const repo = queryRunner.manager.getRepository(entity);
|
||||
|
||||
let selectQueryBuilder = repo.createQueryBuilder('entity');
|
||||
const whereOptions: any = { id };
|
||||
|
||||
selectQueryBuilder = selectQueryBuilder.where('entity.id = :id', { id })
|
||||
.orderBy('entity.block_number', 'DESC')
|
||||
.limit(1);
|
||||
|
||||
// Use blockHash if provided.
|
||||
if (blockHash) {
|
||||
// Fetching blockHash for previous entity in frothy region.
|
||||
const { blockHash: entityblockHash, blockNumber, id: frothyId } = await this._baseDatabase.getFrothyEntity(queryRunner, repo, { blockHash, id });
|
||||
|
||||
if (frothyId) {
|
||||
// If entity found in frothy region.
|
||||
selectQueryBuilder = selectQueryBuilder.andWhere('entity.block_hash = :entityblockHash', { entityblockHash });
|
||||
} else {
|
||||
// If entity not in frothy region.
|
||||
const canonicalBlockNumber = blockNumber + 1;
|
||||
|
||||
selectQueryBuilder = selectQueryBuilder.innerJoinAndSelect('block_progress', 'block', 'block.block_hash = entity.block_hash')
|
||||
.andWhere('block.is_pruned = false')
|
||||
.andWhere('entity.block_number <= :canonicalBlockNumber', { canonicalBlockNumber });
|
||||
}
|
||||
if (blockNumber) {
|
||||
whereOptions.blockNumber = LessThanOrEqual(blockNumber);
|
||||
}
|
||||
|
||||
// TODO: Implement query for nested relations.
|
||||
Object.entries(relations).forEach(([field, data], index) => {
|
||||
const { entity: relatedEntity, isArray } = data;
|
||||
const alias = `relatedEntity${index}`;
|
||||
if (blockHash) {
|
||||
whereOptions.blockHash = blockHash;
|
||||
const block = await this._baseDatabase.getBlockProgress(queryRunner.manager.getRepository('block_progress'), blockHash);
|
||||
blockNumber = block?.blockNumber;
|
||||
}
|
||||
|
||||
if (isArray) {
|
||||
// For one to many relational field.
|
||||
selectQueryBuilder = selectQueryBuilder.leftJoinAndMapMany(
|
||||
`entity.${field}`,
|
||||
relatedEntity,
|
||||
alias,
|
||||
`${alias}.id IN (SELECT unnest(entity.${field})) AND ${alias}.block_number <= entity.block_number`
|
||||
)
|
||||
.addOrderBy(`${alias}.block_number`, 'DESC');
|
||||
} else {
|
||||
// For one to one relational field.
|
||||
selectQueryBuilder = selectQueryBuilder.leftJoinAndMapOne(
|
||||
`entity.${field}`,
|
||||
relatedEntity,
|
||||
alias,
|
||||
`entity.${field} = ${alias}.id AND ${alias}.block_number <= entity.block_number`
|
||||
)
|
||||
.addOrderBy(`${alias}.block_number`, 'DESC');
|
||||
const findOptions = {
|
||||
where: whereOptions,
|
||||
order: {
|
||||
blockNumber: 'DESC'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return selectQueryBuilder.getOne();
|
||||
let entityData: any = await repo.findOne(findOptions as FindOneOptions<Entity>);
|
||||
|
||||
if (!entityData && findOptions.where.blockHash) {
|
||||
entityData = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
|
||||
}
|
||||
|
||||
if (entityData) {
|
||||
// Populate relational fields.
|
||||
// TODO: Implement query for nested relations.
|
||||
const relationQueryPromises = Object.entries(relations).map(async ([field, data]) => {
|
||||
assert(entityData);
|
||||
const { entity: relatedEntity, isArray, isDerived, field: derivedField } = data;
|
||||
|
||||
const repo = queryRunner.manager.getRepository(relatedEntity);
|
||||
let selectQueryBuilder = repo.createQueryBuilder('entity');
|
||||
|
||||
if (isDerived) {
|
||||
// For derived relational field.
|
||||
selectQueryBuilder = selectQueryBuilder.where(`entity.${derivedField} = :id`, { id: entityData.id });
|
||||
|
||||
if (isArray) {
|
||||
selectQueryBuilder = selectQueryBuilder.distinctOn(['entity.id'])
|
||||
.orderBy('entity.id');
|
||||
} else {
|
||||
selectQueryBuilder = selectQueryBuilder.limit(1);
|
||||
}
|
||||
} else {
|
||||
if (isArray) {
|
||||
// For one to many relational field.
|
||||
selectQueryBuilder = selectQueryBuilder.where('entity.id IN (:...ids)', { ids: entityData[field] })
|
||||
.distinctOn(['entity.id'])
|
||||
.orderBy('entity.id');
|
||||
|
||||
// Subquery example if distinctOn is not performant.
|
||||
//
|
||||
// SELECT c.*
|
||||
// FROM
|
||||
// categories c,
|
||||
// (
|
||||
// SELECT id, MAX(block_number) as block_number
|
||||
// FROM categories
|
||||
// WHERE
|
||||
// id IN ('nature', 'tech', 'issues')
|
||||
// AND
|
||||
// block_number <= 127
|
||||
// GROUP BY id
|
||||
// ) a
|
||||
// WHERE
|
||||
// c.id = a.id AND c.block_number = a.block_number
|
||||
} else {
|
||||
// For one to one relational field.
|
||||
selectQueryBuilder = selectQueryBuilder.where('entity.id = :id', { id: entityData[field] })
|
||||
.limit(1);
|
||||
}
|
||||
|
||||
selectQueryBuilder = selectQueryBuilder.addOrderBy('entity.block_number', 'DESC');
|
||||
}
|
||||
|
||||
if (blockNumber) {
|
||||
selectQueryBuilder = selectQueryBuilder.andWhere(
|
||||
'entity.block_number <= :blockNumber',
|
||||
{ blockNumber }
|
||||
);
|
||||
}
|
||||
|
||||
if (isArray) {
|
||||
entityData[field] = await selectQueryBuilder.getMany();
|
||||
} else {
|
||||
entityData[field] = await selectQueryBuilder.getOne();
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(relationQueryPromises);
|
||||
}
|
||||
|
||||
return entityData;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
|
@ -53,7 +53,13 @@ export interface Context {
|
||||
}
|
||||
}
|
||||
|
||||
export const instantiate = async (database: Database, indexer: IndexerInterface, context: Context, filePath: string, data: GraphData = {}): Promise<loader.ResultObject & { exports: any }> => {
|
||||
export const instantiate = async (
|
||||
database: Database,
|
||||
indexer: IndexerInterface,
|
||||
context: Context,
|
||||
filePath: string,
|
||||
data: GraphData = {}
|
||||
): Promise<loader.ResultObject & { exports: any }> => {
|
||||
const { abis = {}, dataSource } = data;
|
||||
const buffer = await fs.readFile(filePath);
|
||||
const provider = getDefaultProvider(NETWORK_URL);
|
||||
|
@ -11,7 +11,7 @@ import { ContractInterface, utils } from 'ethers';
|
||||
|
||||
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import { IndexerInterface, getFullBlock } from '@vulcanize/util';
|
||||
import { IndexerInterface, getFullBlock, BlockHeight } from '@vulcanize/util';
|
||||
|
||||
import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts } from './utils';
|
||||
import { Context, instantiate } from './loader';
|
||||
@ -177,9 +177,9 @@ export class GraphWatcher {
|
||||
this._indexer = indexer;
|
||||
}
|
||||
|
||||
async getEntity<Entity> (entity: new () => Entity, id: string, relations: { [key: string]: any }, blockHash?: string): Promise<any> {
|
||||
async getEntity<Entity> (entity: new () => Entity, id: string, relations: { [key: string]: any }, block?: BlockHeight): Promise<any> {
|
||||
// Get entity from the database.
|
||||
const result = await this._database.getEntityWithRelations(entity, id, relations, blockHash) as any;
|
||||
const result = await this._database.getEntityWithRelations(entity, id, relations, block) as any;
|
||||
|
||||
// Resolve any field name conflicts in the entity result.
|
||||
return resolveEntityFieldConflicts(result);
|
||||
|
@ -12,30 +12,33 @@ import {
|
||||
BigDecimal
|
||||
} from "@graphprotocol/graph-ts";
|
||||
|
||||
export class RelatedEntity extends Entity {
|
||||
export class Blog extends Entity {
|
||||
constructor(id: string) {
|
||||
super();
|
||||
this.set("id", Value.fromString(id));
|
||||
|
||||
this.set("paramBigInt", Value.fromBigInt(BigInt.zero()));
|
||||
this.set("bigIntArray", Value.fromBigIntArray(new Array(0)));
|
||||
this.set("kind", Value.fromString(""));
|
||||
this.set("isActive", Value.fromBoolean(false));
|
||||
this.set("reviews", Value.fromBigIntArray(new Array(0)));
|
||||
this.set("author", Value.fromString(""));
|
||||
this.set("categories", Value.fromStringArray(new Array(0)));
|
||||
}
|
||||
|
||||
save(): void {
|
||||
let id = this.get("id");
|
||||
assert(id != null, "Cannot save RelatedEntity entity without an ID");
|
||||
assert(id != null, "Cannot save Blog entity without an ID");
|
||||
if (id) {
|
||||
assert(
|
||||
id.kind == ValueKind.STRING,
|
||||
"Cannot save RelatedEntity entity with non-string ID. " +
|
||||
"Cannot save Blog entity with non-string ID. " +
|
||||
'Considering using .toHex() to convert the "id" to a string.'
|
||||
);
|
||||
store.set("RelatedEntity", id.toString(), this);
|
||||
store.set("Blog", id.toString(), this);
|
||||
}
|
||||
}
|
||||
|
||||
static load(id: string): RelatedEntity | null {
|
||||
return changetype<RelatedEntity | null>(store.get("RelatedEntity", id));
|
||||
static load(id: string): Blog | null {
|
||||
return changetype<Blog | null>(store.get("Blog", id));
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
@ -47,56 +50,79 @@ export class RelatedEntity extends Entity {
|
||||
this.set("id", Value.fromString(value));
|
||||
}
|
||||
|
||||
get paramBigInt(): BigInt {
|
||||
let value = this.get("paramBigInt");
|
||||
return value!.toBigInt();
|
||||
get kind(): string {
|
||||
let value = this.get("kind");
|
||||
return value!.toString();
|
||||
}
|
||||
|
||||
set paramBigInt(value: BigInt) {
|
||||
this.set("paramBigInt", Value.fromBigInt(value));
|
||||
set kind(value: string) {
|
||||
this.set("kind", Value.fromString(value));
|
||||
}
|
||||
|
||||
get bigIntArray(): Array<BigInt> {
|
||||
let value = this.get("bigIntArray");
|
||||
get isActive(): boolean {
|
||||
let value = this.get("isActive");
|
||||
return value!.toBoolean();
|
||||
}
|
||||
|
||||
set isActive(value: boolean) {
|
||||
this.set("isActive", Value.fromBoolean(value));
|
||||
}
|
||||
|
||||
get reviews(): Array<BigInt> {
|
||||
let value = this.get("reviews");
|
||||
return value!.toBigIntArray();
|
||||
}
|
||||
|
||||
set bigIntArray(value: Array<BigInt>) {
|
||||
this.set("bigIntArray", Value.fromBigIntArray(value));
|
||||
set reviews(value: Array<BigInt>) {
|
||||
this.set("reviews", Value.fromBigIntArray(value));
|
||||
}
|
||||
|
||||
get author(): string {
|
||||
let value = this.get("author");
|
||||
return value!.toString();
|
||||
}
|
||||
|
||||
set author(value: string) {
|
||||
this.set("author", Value.fromString(value));
|
||||
}
|
||||
|
||||
get categories(): Array<string> {
|
||||
let value = this.get("categories");
|
||||
return value!.toStringArray();
|
||||
}
|
||||
|
||||
set categories(value: Array<string>) {
|
||||
this.set("categories", Value.fromStringArray(value));
|
||||
}
|
||||
}
|
||||
|
||||
export class ExampleEntity extends Entity {
|
||||
export class Author extends Entity {
|
||||
constructor(id: string) {
|
||||
super();
|
||||
this.set("id", Value.fromString(id));
|
||||
|
||||
this.set("count", Value.fromBigInt(BigInt.zero()));
|
||||
this.set("paramString", Value.fromString(""));
|
||||
this.set("blogCount", Value.fromBigInt(BigInt.zero()));
|
||||
this.set("name", Value.fromString(""));
|
||||
this.set("rating", Value.fromBigDecimal(BigDecimal.zero()));
|
||||
this.set("paramInt", Value.fromI32(0));
|
||||
this.set("paramBoolean", Value.fromBoolean(false));
|
||||
this.set("paramBytes", Value.fromBytes(Bytes.empty()));
|
||||
this.set("paramEnum", Value.fromString(""));
|
||||
this.set("paramBigDecimal", Value.fromBigDecimal(BigDecimal.zero()));
|
||||
this.set("related", Value.fromString(""));
|
||||
this.set("manyRelated", Value.fromStringArray(new Array(0)));
|
||||
}
|
||||
|
||||
save(): void {
|
||||
let id = this.get("id");
|
||||
assert(id != null, "Cannot save ExampleEntity entity without an ID");
|
||||
assert(id != null, "Cannot save Author entity without an ID");
|
||||
if (id) {
|
||||
assert(
|
||||
id.kind == ValueKind.STRING,
|
||||
"Cannot save ExampleEntity entity with non-string ID. " +
|
||||
"Cannot save Author entity with non-string ID. " +
|
||||
'Considering using .toHex() to convert the "id" to a string.'
|
||||
);
|
||||
store.set("ExampleEntity", id.toString(), this);
|
||||
store.set("Author", id.toString(), this);
|
||||
}
|
||||
}
|
||||
|
||||
static load(id: string): ExampleEntity | null {
|
||||
return changetype<ExampleEntity | null>(store.get("ExampleEntity", id));
|
||||
static load(id: string): Author | null {
|
||||
return changetype<Author | null>(store.get("Author", id));
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
@ -108,22 +134,31 @@ export class ExampleEntity extends Entity {
|
||||
this.set("id", Value.fromString(value));
|
||||
}
|
||||
|
||||
get count(): BigInt {
|
||||
let value = this.get("count");
|
||||
get blogCount(): BigInt {
|
||||
let value = this.get("blogCount");
|
||||
return value!.toBigInt();
|
||||
}
|
||||
|
||||
set count(value: BigInt) {
|
||||
this.set("count", Value.fromBigInt(value));
|
||||
set blogCount(value: BigInt) {
|
||||
this.set("blogCount", Value.fromBigInt(value));
|
||||
}
|
||||
|
||||
get paramString(): string {
|
||||
let value = this.get("paramString");
|
||||
get name(): string {
|
||||
let value = this.get("name");
|
||||
return value!.toString();
|
||||
}
|
||||
|
||||
set paramString(value: string) {
|
||||
this.set("paramString", Value.fromString(value));
|
||||
set name(value: string) {
|
||||
this.set("name", Value.fromString(value));
|
||||
}
|
||||
|
||||
get rating(): BigDecimal {
|
||||
let value = this.get("rating");
|
||||
return value!.toBigDecimal();
|
||||
}
|
||||
|
||||
set rating(value: BigDecimal) {
|
||||
this.set("rating", Value.fromBigDecimal(value));
|
||||
}
|
||||
|
||||
get paramInt(): i32 {
|
||||
@ -135,15 +170,6 @@ export class ExampleEntity extends Entity {
|
||||
this.set("paramInt", Value.fromI32(value));
|
||||
}
|
||||
|
||||
get paramBoolean(): boolean {
|
||||
let value = this.get("paramBoolean");
|
||||
return value!.toBoolean();
|
||||
}
|
||||
|
||||
set paramBoolean(value: boolean) {
|
||||
this.set("paramBoolean", Value.fromBoolean(value));
|
||||
}
|
||||
|
||||
get paramBytes(): Bytes {
|
||||
let value = this.get("paramBytes");
|
||||
return value!.toBytes();
|
||||
@ -153,68 +179,40 @@ export class ExampleEntity extends Entity {
|
||||
this.set("paramBytes", Value.fromBytes(value));
|
||||
}
|
||||
|
||||
get paramEnum(): string {
|
||||
let value = this.get("paramEnum");
|
||||
return value!.toString();
|
||||
}
|
||||
|
||||
set paramEnum(value: string) {
|
||||
this.set("paramEnum", Value.fromString(value));
|
||||
}
|
||||
|
||||
get paramBigDecimal(): BigDecimal {
|
||||
let value = this.get("paramBigDecimal");
|
||||
return value!.toBigDecimal();
|
||||
}
|
||||
|
||||
set paramBigDecimal(value: BigDecimal) {
|
||||
this.set("paramBigDecimal", Value.fromBigDecimal(value));
|
||||
}
|
||||
|
||||
get related(): string {
|
||||
let value = this.get("related");
|
||||
return value!.toString();
|
||||
}
|
||||
|
||||
set related(value: string) {
|
||||
this.set("related", Value.fromString(value));
|
||||
}
|
||||
|
||||
get manyRelated(): Array<string> {
|
||||
let value = this.get("manyRelated");
|
||||
get blogs(): Array<string> {
|
||||
let value = this.get("blogs");
|
||||
return value!.toStringArray();
|
||||
}
|
||||
|
||||
set manyRelated(value: Array<string>) {
|
||||
this.set("manyRelated", Value.fromStringArray(value));
|
||||
set blogs(value: Array<string>) {
|
||||
this.set("blogs", Value.fromStringArray(value));
|
||||
}
|
||||
}
|
||||
|
||||
export class ManyRelatedEntity extends Entity {
|
||||
export class Category extends Entity {
|
||||
constructor(id: string) {
|
||||
super();
|
||||
this.set("id", Value.fromString(id));
|
||||
|
||||
this.set("name", Value.fromString(""));
|
||||
this.set("count", Value.fromBigInt(BigInt.zero()));
|
||||
}
|
||||
|
||||
save(): void {
|
||||
let id = this.get("id");
|
||||
assert(id != null, "Cannot save ManyRelatedEntity entity without an ID");
|
||||
assert(id != null, "Cannot save Category entity without an ID");
|
||||
if (id) {
|
||||
assert(
|
||||
id.kind == ValueKind.STRING,
|
||||
"Cannot save ManyRelatedEntity entity with non-string ID. " +
|
||||
"Cannot save Category entity with non-string ID. " +
|
||||
'Considering using .toHex() to convert the "id" to a string.'
|
||||
);
|
||||
store.set("ManyRelatedEntity", id.toString(), this);
|
||||
store.set("Category", id.toString(), this);
|
||||
}
|
||||
}
|
||||
|
||||
static load(id: string): ManyRelatedEntity | null {
|
||||
return changetype<ManyRelatedEntity | null>(
|
||||
store.get("ManyRelatedEntity", id)
|
||||
);
|
||||
static load(id: string): Category | null {
|
||||
return changetype<Category | null>(store.get("Category", id));
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
@ -226,6 +224,15 @@ export class ManyRelatedEntity extends Entity {
|
||||
this.set("id", Value.fromString(value));
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
let value = this.get("name");
|
||||
return value!.toString();
|
||||
}
|
||||
|
||||
set name(value: string) {
|
||||
this.set("name", Value.fromString(value));
|
||||
}
|
||||
|
||||
get count(): BigInt {
|
||||
let value = this.get("count");
|
||||
return value!.toBigInt();
|
||||
|
@ -1,28 +1,29 @@
|
||||
enum EnumType {
|
||||
choice1
|
||||
choice2
|
||||
enum BlogKind {
|
||||
short
|
||||
long
|
||||
}
|
||||
|
||||
type RelatedEntity @entity {
|
||||
type Blog @entity {
|
||||
id: ID!
|
||||
paramBigInt: BigInt!
|
||||
bigIntArray: [BigInt!]!
|
||||
kind: BlogKind!
|
||||
isActive: Boolean!
|
||||
reviews: [BigInt!]!
|
||||
author: Author!
|
||||
categories: [Category!]!
|
||||
}
|
||||
|
||||
type ExampleEntity @entity {
|
||||
type Author @entity {
|
||||
id: ID!
|
||||
count: BigInt!
|
||||
paramString: String! # string
|
||||
blogCount: BigInt!
|
||||
name: String! # string
|
||||
rating: BigDecimal!
|
||||
paramInt: Int! # uint8
|
||||
paramBoolean: Boolean!
|
||||
paramBytes: Bytes!
|
||||
paramEnum: EnumType!
|
||||
paramBigDecimal: BigDecimal!
|
||||
related: RelatedEntity!
|
||||
manyRelated: [ManyRelatedEntity!]!
|
||||
blogs: [Blog!]! @derivedFrom(field: "author")
|
||||
}
|
||||
|
||||
type ManyRelatedEntity @entity {
|
||||
type Category @entity {
|
||||
id: ID!
|
||||
name: String!
|
||||
count: BigInt!
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
Example1,
|
||||
Test
|
||||
} from '../generated/Example1/Example1';
|
||||
import { ExampleEntity, ManyRelatedEntity, RelatedEntity } from '../generated/schema';
|
||||
import { Author, Blog, Category } from '../generated/schema';
|
||||
|
||||
export function handleTest (event: Test): void {
|
||||
log.debug('event.address: {}', [event.address.toHexString()]);
|
||||
@ -15,52 +15,54 @@ export function handleTest (event: Test): void {
|
||||
|
||||
// Entities can be loaded from the store using a string ID; this ID
|
||||
// needs to be unique across all entities of the same type
|
||||
let entity = ExampleEntity.load(event.transaction.from.toHex());
|
||||
let author = Author.load(event.transaction.from.toHex());
|
||||
|
||||
// Entities only exist after they have been saved to the store;
|
||||
// `null` checks allow to create entities on demand
|
||||
if (!entity) {
|
||||
entity = new ExampleEntity(event.transaction.from.toHex());
|
||||
if (!author) {
|
||||
author = new Author(event.transaction.from.toHex());
|
||||
|
||||
// Entity fields can be set using simple assignments
|
||||
entity.count = BigInt.fromString('0');
|
||||
author.blogCount = BigInt.fromString('0');
|
||||
}
|
||||
|
||||
// BigInt and BigDecimal math are supported
|
||||
entity.count = entity.count + BigInt.fromString('1');
|
||||
author.blogCount = author.blogCount + BigInt.fromString('1');
|
||||
|
||||
// Entity fields can be set based on event parameters
|
||||
entity.paramString = event.params.param1;
|
||||
entity.paramInt = event.params.param2;
|
||||
entity.paramBoolean = true;
|
||||
entity.paramBytes = event.address;
|
||||
entity.paramEnum = 'choice1';
|
||||
entity.paramBigDecimal = BigDecimal.fromString('123');
|
||||
|
||||
let relatedEntity = RelatedEntity.load(event.params.param1);
|
||||
|
||||
if (!relatedEntity) {
|
||||
relatedEntity = new RelatedEntity(event.params.param1);
|
||||
relatedEntity.paramBigInt = BigInt.fromString('123');
|
||||
}
|
||||
|
||||
const bigIntArray = relatedEntity.bigIntArray;
|
||||
bigIntArray.push(entity.count);
|
||||
relatedEntity.bigIntArray = bigIntArray;
|
||||
|
||||
relatedEntity.save();
|
||||
entity.related = relatedEntity.id;
|
||||
|
||||
const manyRelatedEntity = new ManyRelatedEntity(event.transaction.hash.toHexString());
|
||||
manyRelatedEntity.count = entity.count;
|
||||
manyRelatedEntity.save();
|
||||
|
||||
const manyRelated = entity.manyRelated;
|
||||
manyRelated.push(manyRelatedEntity.id);
|
||||
entity.manyRelated = manyRelated;
|
||||
author.name = event.params.param1;
|
||||
author.paramInt = event.params.param2;
|
||||
author.paramBytes = event.address;
|
||||
author.rating = BigDecimal.fromString('4');
|
||||
|
||||
// Entities can be written to the store with `.save()`
|
||||
entity.save();
|
||||
author.save();
|
||||
|
||||
let category = Category.load(author.blogCount.toString());
|
||||
|
||||
if (!category) {
|
||||
category = new Category(author.blogCount.toString());
|
||||
category.name = event.params.param1;
|
||||
}
|
||||
|
||||
category.count = category.count + BigInt.fromString('1');
|
||||
category.save();
|
||||
|
||||
const blog = new Blog(event.transaction.hash.toHexString());
|
||||
blog.kind = 'long';
|
||||
blog.isActive = true;
|
||||
|
||||
const blogReviews = blog.reviews;
|
||||
blogReviews.push(BigInt.fromString('4'));
|
||||
blog.reviews = blogReviews;
|
||||
|
||||
blog.author = author.id;
|
||||
|
||||
const categories = blog.categories;
|
||||
categories.push(category.id);
|
||||
blog.categories = categories;
|
||||
|
||||
blog.save();
|
||||
|
||||
const contractAddress = dataSource.address();
|
||||
const contract = Example1.bind(contractAddress);
|
||||
|
@ -16,9 +16,9 @@ import { BlockProgress } from '../../entity/BlockProgress';
|
||||
|
||||
import { GetMethod } from '../../entity/GetMethod';
|
||||
import { _Test } from '../../entity/_Test';
|
||||
import { ExampleEntity } from '../../entity/ExampleEntity';
|
||||
import { RelatedEntity } from '../../entity/RelatedEntity';
|
||||
import { ManyRelatedEntity } from '../../entity/ManyRelatedEntity';
|
||||
import { Author } from '../../entity/Author';
|
||||
import { Blog } from '../../entity/Blog';
|
||||
import { Category } from '../../entity/Category';
|
||||
|
||||
const log = debug('vulcanize:reset-state');
|
||||
|
||||
@ -74,7 +74,7 @@ export const handler = async (argv: any): Promise<void> => {
|
||||
const dbTx = await db.createTransactionRunner();
|
||||
|
||||
try {
|
||||
const entities = [BlockProgress, GetMethod, _Test, ExampleEntity, ManyRelatedEntity, RelatedEntity];
|
||||
const entities = [BlockProgress, GetMethod, _Test, Author, Category, Blog];
|
||||
|
||||
const removeEntitiesPromise = entities.map(async entityClass => {
|
||||
return db.removeEntities<any>(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) });
|
||||
|
@ -7,13 +7,8 @@ import Decimal from 'decimal.js';
|
||||
|
||||
import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
enum EnumType {
|
||||
choice1 = 'choice1',
|
||||
choice2 = 'choice2'
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class ExampleEntity {
|
||||
export class Author {
|
||||
@PrimaryColumn('varchar')
|
||||
id!: string;
|
||||
|
||||
@ -24,33 +19,17 @@ export class ExampleEntity {
|
||||
blockNumber!: number;
|
||||
|
||||
@Column('bigint', { transformer: bigintTransformer })
|
||||
count!: bigint;
|
||||
blogCount!: bigint;
|
||||
|
||||
@Column('varchar')
|
||||
paramString!: string
|
||||
name!: string
|
||||
|
||||
@Column('integer')
|
||||
paramInt!: number
|
||||
|
||||
@Column('boolean')
|
||||
paramBoolean!: boolean
|
||||
|
||||
@Column('varchar')
|
||||
paramBytes!: string
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: EnumType,
|
||||
default: EnumType.choice1
|
||||
})
|
||||
paramEnum!: EnumType
|
||||
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
paramBigDecimal!: Decimal
|
||||
|
||||
@Column('varchar')
|
||||
related!: string;
|
||||
|
||||
@Column('varchar', { array: true })
|
||||
manyRelated!: string[]
|
||||
rating!: Decimal
|
||||
}
|
43
packages/graph-test-watcher/src/entity/Blog.ts
Normal file
43
packages/graph-test-watcher/src/entity/Blog.ts
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
|
||||
import { bigintArrayTransformer } from '@vulcanize/util';
|
||||
|
||||
enum BlogType {
|
||||
short = 'short',
|
||||
long = 'long'
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Blog {
|
||||
@PrimaryColumn('varchar')
|
||||
id!: string;
|
||||
|
||||
@PrimaryColumn('varchar', { length: 66 })
|
||||
blockHash!: string;
|
||||
|
||||
@Column('integer')
|
||||
blockNumber!: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: BlogType,
|
||||
default: BlogType.short
|
||||
})
|
||||
kind!: BlogType
|
||||
|
||||
@Column('boolean')
|
||||
isActive!: boolean
|
||||
|
||||
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
|
||||
reviews!: bigint[];
|
||||
|
||||
@Column('varchar')
|
||||
author!: string;
|
||||
|
||||
@Column('varchar', { array: true })
|
||||
categories!: string[]
|
||||
}
|
@ -7,7 +7,7 @@ import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
import { bigintTransformer } from '@vulcanize/util';
|
||||
|
||||
@Entity()
|
||||
export class ManyRelatedEntity {
|
||||
export class Category {
|
||||
@PrimaryColumn('varchar')
|
||||
id!: string;
|
||||
|
||||
@ -19,4 +19,7 @@ export class ManyRelatedEntity {
|
||||
|
||||
@Column('bigint', { transformer: bigintTransformer })
|
||||
count!: bigint;
|
||||
|
||||
@Column('varchar')
|
||||
name!: string;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
|
||||
import { bigintTransformer, bigintArrayTransformer } from '@vulcanize/util';
|
||||
|
||||
@Entity()
|
||||
export class RelatedEntity {
|
||||
@PrimaryColumn('varchar')
|
||||
id!: string;
|
||||
|
||||
@PrimaryColumn('varchar', { length: 66 })
|
||||
blockHash!: string;
|
||||
|
||||
@Column('integer')
|
||||
blockNumber!: number;
|
||||
|
||||
@Column('bigint', { transformer: bigintTransformer })
|
||||
paramBigInt!: bigint;
|
||||
|
||||
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
|
||||
bigIntArray!: bigint[];
|
||||
}
|
16
packages/graph-test-watcher/src/gql/queries/author.gql
Normal file
16
packages/graph-test-watcher/src/gql/queries/author.gql
Normal file
@ -0,0 +1,16 @@
|
||||
query author($id: String!, $blockHash: Bytes!){
|
||||
author(id: $id, block: { hash: $blockHash }){
|
||||
id
|
||||
blogCount
|
||||
name
|
||||
rating
|
||||
paramInt
|
||||
paramBytes
|
||||
blogs {
|
||||
id
|
||||
kind
|
||||
reviews
|
||||
isActive
|
||||
}
|
||||
}
|
||||
}
|
17
packages/graph-test-watcher/src/gql/queries/blog.gql
Normal file
17
packages/graph-test-watcher/src/gql/queries/blog.gql
Normal file
@ -0,0 +1,17 @@
|
||||
query blog($id: String!, $blockHash: Bytes!){
|
||||
blog(id: $id, block: { hash: $blockHash }){
|
||||
id
|
||||
kind
|
||||
reviews
|
||||
isActive
|
||||
author {
|
||||
id
|
||||
name
|
||||
}
|
||||
categories {
|
||||
id
|
||||
name
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
7
packages/graph-test-watcher/src/gql/queries/category.gql
Normal file
7
packages/graph-test-watcher/src/gql/queries/category.gql
Normal file
@ -0,0 +1,7 @@
|
||||
query category($id: String!, $blockHash: Bytes!){
|
||||
category(id: $id, block: { hash: $blockHash }){
|
||||
id
|
||||
count
|
||||
name
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
query exampleEntity($id: String!, $blockHash: Bytes!){
|
||||
exampleEntity(id: $id, block: { hash: $blockHash }){
|
||||
id
|
||||
count
|
||||
paramString
|
||||
paramInt
|
||||
paramBoolean
|
||||
paramBytes
|
||||
paramEnum
|
||||
paramBigDecimal
|
||||
related {
|
||||
id
|
||||
paramBigInt
|
||||
bigIntArray
|
||||
}
|
||||
manyRelated {
|
||||
id
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
query manyRelatedEntity($id: String!, $blockHash: Bytes!){
|
||||
manyRelatedEntity(id: $id, block: { hash: $blockHash }){
|
||||
id
|
||||
count
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
query relatedEntity($id: String!, $blockHash: Bytes!){
|
||||
relatedEntity(id: $id, block: { hash: $blockHash }){
|
||||
id
|
||||
paramBigInt
|
||||
bigIntArray
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import { BaseProvider } from '@ethersproject/providers';
|
||||
import * as codec from '@ipld/dag-cbor';
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import { StorageLayout } from '@vulcanize/solidity-mapper';
|
||||
import { EventInterface, Indexer as BaseIndexer, IndexerInterface, ValueResult, UNKNOWN_EVENT_NAME, ServerConfig, updateStateForElementaryType, JobQueue } from '@vulcanize/util';
|
||||
import { EventInterface, Indexer as BaseIndexer, IndexerInterface, ValueResult, UNKNOWN_EVENT_NAME, ServerConfig, updateStateForElementaryType, JobQueue, BlockHeight } from '@vulcanize/util';
|
||||
import { GraphWatcher } from '@vulcanize/graph-node';
|
||||
|
||||
import { Database } from './database';
|
||||
@ -29,9 +29,9 @@ import { IPLDBlock } from './entity/IPLDBlock';
|
||||
import artifacts from './artifacts/Example.json';
|
||||
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
||||
import { IPFSClient } from './ipfs';
|
||||
import { ExampleEntity } from './entity/ExampleEntity';
|
||||
import { RelatedEntity } from './entity/RelatedEntity';
|
||||
import { ManyRelatedEntity } from './entity/ManyRelatedEntity';
|
||||
import { Author } from './entity/Author';
|
||||
import { Blog } from './entity/Blog';
|
||||
import { Category } from './entity/Category';
|
||||
|
||||
const log = debug('vulcanize:indexer');
|
||||
|
||||
@ -546,10 +546,10 @@ export class Indexer implements IndexerInterface {
|
||||
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
|
||||
}
|
||||
|
||||
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash?: string): Promise<Entity | undefined> {
|
||||
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, block: BlockHeight): Promise<Entity | undefined> {
|
||||
const relations = this._relationsMap.get(entity) || {};
|
||||
|
||||
const data = await this._graphWatcher.getEntity(entity, id, relations, blockHash);
|
||||
const data = await this._graphWatcher.getEntity(entity, id, relations, block);
|
||||
|
||||
return data;
|
||||
}
|
||||
@ -739,13 +739,24 @@ export class Indexer implements IndexerInterface {
|
||||
|
||||
_populateRelationsMap (): void {
|
||||
// Needs to be generated by codegen.
|
||||
this._relationsMap.set(ExampleEntity, {
|
||||
related: {
|
||||
entity: RelatedEntity,
|
||||
this._relationsMap.set(Author, {
|
||||
blogs: {
|
||||
entity: Blog,
|
||||
isDerived: true,
|
||||
isArray: true,
|
||||
field: 'author'
|
||||
}
|
||||
});
|
||||
|
||||
this._relationsMap.set(Blog, {
|
||||
author: {
|
||||
entity: Author,
|
||||
isDerived: false,
|
||||
isArray: false
|
||||
},
|
||||
manyRelated: {
|
||||
entity: ManyRelatedEntity,
|
||||
categories: {
|
||||
entity: Category,
|
||||
isDerived: false,
|
||||
isArray: true
|
||||
}
|
||||
});
|
||||
|
@ -11,9 +11,9 @@ import { ValueResult, BlockHeight } from '@vulcanize/util';
|
||||
import { Indexer } from './indexer';
|
||||
import { EventWatcher } from './events';
|
||||
|
||||
import { ExampleEntity } from './entity/ExampleEntity';
|
||||
import { RelatedEntity } from './entity/RelatedEntity';
|
||||
import { ManyRelatedEntity } from './entity/ManyRelatedEntity';
|
||||
import { Author } from './entity/Author';
|
||||
import { Blog } from './entity/Blog';
|
||||
import { Category } from './entity/Category';
|
||||
|
||||
const log = debug('vulcanize:resolver');
|
||||
|
||||
@ -57,22 +57,22 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
|
||||
return indexer._test(blockHash, contractAddress);
|
||||
},
|
||||
|
||||
relatedEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<RelatedEntity | undefined> => {
|
||||
log('relatedEntity', id, block);
|
||||
blog: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<Blog | undefined> => {
|
||||
log('blog', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(RelatedEntity, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Blog, id, block);
|
||||
},
|
||||
|
||||
manyRelatedEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<ManyRelatedEntity | undefined> => {
|
||||
log('relatedEntity', id, block);
|
||||
category: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<Category | undefined> => {
|
||||
log('category', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(ManyRelatedEntity, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Category, id, block);
|
||||
},
|
||||
|
||||
exampleEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<ExampleEntity | undefined> => {
|
||||
log('exampleEntity', id, block);
|
||||
author: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<Author | undefined> => {
|
||||
log('author', id, block);
|
||||
|
||||
return indexer.getSubgraphEntity(ExampleEntity, id, block.hash);
|
||||
return indexer.getSubgraphEntity(Author, id, block);
|
||||
},
|
||||
|
||||
events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => {
|
||||
|
@ -77,40 +77,41 @@ type Query {
|
||||
eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!]
|
||||
getMethod(blockHash: String!, contractAddress: String!): ResultString!
|
||||
_test(blockHash: String!, contractAddress: String!): ResultBigInt!
|
||||
relatedEntity(id: String!, block: Block_height): RelatedEntity!
|
||||
exampleEntity(id: String!, block: Block_height): ExampleEntity!
|
||||
manyRelatedEntity(id: String!, block: Block_height): ManyRelatedEntity!
|
||||
blog(id: String!, block: Block_height): Blog!
|
||||
author(id: String!, block: Block_height): Author!
|
||||
category(id: String!, block: Block_height): Category!
|
||||
getStateByCID(cid: String!): ResultIPLDBlock
|
||||
getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock
|
||||
}
|
||||
|
||||
enum EnumType {
|
||||
choice1
|
||||
choice2
|
||||
enum BlogKind {
|
||||
short
|
||||
long
|
||||
}
|
||||
|
||||
type RelatedEntity {
|
||||
type Blog {
|
||||
id: ID!
|
||||
paramBigInt: BigInt!
|
||||
bigIntArray: [BigInt!]!
|
||||
kind: BlogKind!
|
||||
isActive: Boolean!
|
||||
reviews: [BigInt!]!
|
||||
author: Author!
|
||||
categories: [Category!]!
|
||||
}
|
||||
|
||||
type ManyRelatedEntity {
|
||||
type Category {
|
||||
id: ID!
|
||||
count: BigInt!
|
||||
name: String!
|
||||
}
|
||||
|
||||
type ExampleEntity {
|
||||
type Author {
|
||||
id: ID!
|
||||
count: BigInt!
|
||||
paramString: String!
|
||||
blogCount: BigInt!
|
||||
name: String!
|
||||
rating: BigDecimal!
|
||||
paramInt: Int!
|
||||
paramBoolean: Boolean!
|
||||
paramBytes: Bytes!
|
||||
paramEnum: EnumType!
|
||||
paramBigDecimal: BigDecimal!
|
||||
related: RelatedEntity!
|
||||
manyRelated: [ManyRelatedEntity!]!
|
||||
blogs: [Blog!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
Loading…
Reference in New Issue
Block a user