Initial commit
This commit is contained in:
327
skills/hyperindex-development/references/graphql-querying.md
Normal file
327
skills/hyperindex-development/references/graphql-querying.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# GraphQL Querying
|
||||
|
||||
Query indexed data via GraphQL. Works locally during development or on hosted deployments.
|
||||
|
||||
**Local endpoint:** `http://localhost:8080/v1/graphql`
|
||||
**Hasura Console:** `http://localhost:8080` (password: `testing`)
|
||||
|
||||
## Checking Indexing Progress
|
||||
|
||||
**Always check sync status first** before assuming data is missing.
|
||||
|
||||
### Using `_meta` (Recommended)
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ _meta { chainId startBlock progressBlock sourceBlock eventsProcessed isReady } }"}'
|
||||
```
|
||||
|
||||
Or in GraphQL:
|
||||
```graphql
|
||||
{
|
||||
_meta {
|
||||
chainId
|
||||
startBlock
|
||||
progressBlock
|
||||
sourceBlock
|
||||
eventsProcessed
|
||||
isReady
|
||||
readyAt
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `progressBlock` - Last fully processed block
|
||||
- `sourceBlock` - Latest known block from data source (target)
|
||||
- `eventsProcessed` - Total events processed
|
||||
- `isReady` - `true` when historical sync is complete
|
||||
- `readyAt` - Timestamp when sync finished
|
||||
|
||||
**Example response:**
|
||||
```json
|
||||
{
|
||||
"_meta": [
|
||||
{
|
||||
"chainId": 1,
|
||||
"progressBlock": 22817138,
|
||||
"sourceBlock": 23368264,
|
||||
"eventsProcessed": 2380000,
|
||||
"isReady": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Filter by Chain
|
||||
|
||||
```graphql
|
||||
{
|
||||
_meta(where: { chainId: { _eq: 1 } }) {
|
||||
progressBlock
|
||||
isReady
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using `chain_metadata`
|
||||
|
||||
More detailed chain information:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ chain_metadata { chain_id start_block latest_processed_block num_events_processed is_hyper_sync } }"}'
|
||||
```
|
||||
|
||||
**Additional fields:**
|
||||
- `is_hyper_sync` - Whether using HyperSync (fast) or RPC
|
||||
- `latest_fetched_block_number` - Latest block fetched from source
|
||||
- `num_batches_fetched` - Number of batches processed
|
||||
|
||||
## Basic Queries
|
||||
|
||||
### Query Entities
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ Transfer(limit: 10) { id from to amount blockNumber } }"}'
|
||||
```
|
||||
|
||||
### With Ordering
|
||||
|
||||
```graphql
|
||||
{
|
||||
Transfer(order_by: { blockNumber: desc }, limit: 10) {
|
||||
id
|
||||
from
|
||||
to
|
||||
amount
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### With Filters
|
||||
|
||||
```graphql
|
||||
{
|
||||
Transfer(where: { from: { _eq: "0x123..." } }, limit: 100) {
|
||||
id
|
||||
from
|
||||
to
|
||||
amount
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Filter by Chain (Multichain)
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ Transfer(where: {chainId: {_eq: 42161}}, limit: 10) { id chainId from to amount } }"}'
|
||||
```
|
||||
|
||||
## Filter Operators
|
||||
|
||||
| Operator | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `_eq` | Equals | `{field: {_eq: "value"}}` |
|
||||
| `_neq` | Not equals | `{field: {_neq: "value"}}` |
|
||||
| `_gt` | Greater than | `{amount: {_gt: "100"}}` |
|
||||
| `_gte` | Greater than or equal | `{amount: {_gte: "100"}}` |
|
||||
| `_lt` | Less than | `{amount: {_lt: "100"}}` |
|
||||
| `_lte` | Less than or equal | `{amount: {_lte: "100"}}` |
|
||||
| `_in` | In list | `{chainId: {_in: [1, 10]}}` |
|
||||
| `_nin` | Not in list | `{chainId: {_nin: [1]}}` |
|
||||
| `_is_null` | Is null | `{field: {_is_null: true}}` |
|
||||
| `_like` | Pattern match | `{id: {_like: "1_%"}}` |
|
||||
| `_ilike` | Case-insensitive pattern | `{user: {_ilike: "%abc%"}}` |
|
||||
|
||||
**Important:** BigInt values must be quoted strings: `{amount: {_gt: "1000000000000000000"}}`
|
||||
|
||||
## Logical Operators
|
||||
|
||||
```graphql
|
||||
# AND - all conditions must match
|
||||
where: { _and: [{ chainId: { _eq: 1 } }, { amount: { _gt: "0" } }] }
|
||||
|
||||
# OR - any condition matches
|
||||
where: { _or: [{ from: { _eq: "0x123" } }, { to: { _eq: "0x123" } }] }
|
||||
|
||||
# NOT - negate condition
|
||||
where: { _not: { amount: { _eq: "0" } } }
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
### Limit and Offset
|
||||
|
||||
```graphql
|
||||
{
|
||||
Transfer(limit: 100, offset: 200) {
|
||||
id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cursor-based (by primary key)
|
||||
|
||||
```graphql
|
||||
{
|
||||
Transfer(limit: 100, where: { id: { _gt: "last_seen_id" } }, order_by: { id: asc }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Query Patterns
|
||||
|
||||
### Recent Transfers for User
|
||||
|
||||
```graphql
|
||||
query UserTransfers($address: String!) {
|
||||
Transfer(
|
||||
where: {
|
||||
_or: [
|
||||
{ from: { _eq: $address } },
|
||||
{ to: { _eq: $address } }
|
||||
]
|
||||
}
|
||||
order_by: { blockTimestamp: desc }
|
||||
limit: 50
|
||||
) {
|
||||
id
|
||||
from
|
||||
to
|
||||
amount
|
||||
blockTimestamp
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Polling for Updates
|
||||
|
||||
```graphql
|
||||
query NewTransfers($lastTimestamp: BigInt!) {
|
||||
Transfer(where: { blockTimestamp: { _gt: $lastTimestamp } }) {
|
||||
id
|
||||
from
|
||||
to
|
||||
amount
|
||||
blockTimestamp
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get by Primary Key
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ Transfer_by_pk(id: \"1_0xabc..._0\") { id from to amount } }"}'
|
||||
```
|
||||
|
||||
## Discovering Schema
|
||||
|
||||
### List All Query Types
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ __schema { queryType { fields { name } } } }"}'
|
||||
```
|
||||
|
||||
### Get Entity Fields
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8080/v1/graphql \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ __type(name: \"Transfer\") { fields { name type { name } } } }"}'
|
||||
```
|
||||
|
||||
## Aggregations
|
||||
|
||||
**Local:** Aggregation queries work in Hasura console.
|
||||
|
||||
**Hosted Service:** Aggregations are **disabled** for performance.
|
||||
|
||||
**Best Practice:** Compute aggregates at indexing time:
|
||||
|
||||
```graphql
|
||||
# Schema
|
||||
type GlobalStats {
|
||||
id: ID!
|
||||
totalTransfers: Int!
|
||||
totalVolume: BigDecimal!
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Handler - update on each transfer
|
||||
const stats = await context.GlobalStats.get("global");
|
||||
context.GlobalStats.set({
|
||||
...stats,
|
||||
totalTransfers: stats.totalTransfers + 1,
|
||||
totalVolume: stats.totalVolume.plus(transferAmount),
|
||||
});
|
||||
```
|
||||
|
||||
Then query precomputed values:
|
||||
```graphql
|
||||
{
|
||||
GlobalStats(where: { id: { _eq: "global" } }) {
|
||||
totalTransfers
|
||||
totalVolume
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hasura Console
|
||||
|
||||
Open `http://localhost:8080` for the visual interface.
|
||||
|
||||
**API Tab:**
|
||||
- Execute GraphQL queries
|
||||
- Explorer shows all entities
|
||||
- Test queries before frontend integration
|
||||
|
||||
**Data Tab:**
|
||||
- View database tables directly
|
||||
- Check `db_write_timestamp` for freshness
|
||||
- Manually inspect entities
|
||||
|
||||
## Fetch from Code
|
||||
|
||||
```typescript
|
||||
const response = await fetch('http://localhost:8080/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
Transfer(limit: 10) {
|
||||
id
|
||||
from
|
||||
to
|
||||
amount
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Check `_meta` first** - Verify indexer progress before assuming data is missing
|
||||
2. **Fetch only needed fields** - Reduces response size
|
||||
3. **Use pagination** - Never query unlimited results
|
||||
4. **Filter on indexed fields** - Use `@index` columns for faster queries
|
||||
5. **Poll with timestamps** - Fetch only new data for real-time updates
|
||||
6. **Precompute aggregates** - At indexing time, not query time
|
||||
7. **BigInt as strings** - Always quote large numbers in filters
|
||||
Reference in New Issue
Block a user