Entities Schema (schema.graphql)
The schema.graphql file defines the data model for your HyperIndex indexer. Each entity type defined in this schema corresponds directly to a database table, with your event handlers responsible for creating and updating the records. HyperIndex automatically generates a GraphQL API based on these entity types, allowing easy access to the indexed data.
Defining Entity Types
Entities in your schema are defined as GraphQL object types:
Example:
type User {
id: ID!
greetings: [String!]!
latestGreeting: String!
numberOfGreetings: Int!
}
Requirements:
- Every entity must have a unique
idfield, using one of these scalar types:ID!,String!,Int!,Bytes!, orBigInt!
Scalar Types
Scalar types represent basic data types and map directly to JavaScript, TypeScript, or ReScript types.
| GraphQL Scalar | Description | JavaScript/TypeScript | ReScript |
|---|---|---|---|
ID | Unique identifier | string | string |
String | UTF-8 character sequence | string | string |
Int | Signed 32-bit integer | number | int |
Float | Signed floating-point number | number | float |
Boolean | true or false | boolean | bool |
Bytes | UTF-8 character sequence (hex prefixed 0x) | string | string |
BigInt | Signed integer (int256 in Solidity) | bigint | bigint |
BigDecimal | Arbitrary-size floating-point | BigDecimal (imported) | BigDecimal.t |
Timestamp | Timestamp with timezone | Date | Js.Date.t |
Json | JSON object | Json | Js.Json.t |
Learn more about GraphQL scalars here.
Working with BigDecimal
The BigDecimal scalar type in HyperIndex is based on the bignumber.js library, which provides arbitrary-precision decimal arithmetic. This is essential for financial calculations and handling numeric values that exceed JavaScript's native number precision.
Importing BigDecimal
// JavaScript/TypeScript
import { BigDecimal } from "envio";
// ReScript
open BigDecimal;
Creating BigDecimal Instances
// From string (recommended for precision)
const price = new BigDecimal("123.456789");
// From number (may lose precision for very large values)
const amount = new BigDecimal(123.45);
// From other BigDecimal
const copy = new BigDecimal(price);
Arithmetic Operations
BigDecimal instances are immutable. Operations return new BigDecimal instances:
// Basic arithmetic
const a = new BigDecimal("123.45");
const b = new BigDecimal("67.89");
const sum = a.plus(b); // 191.34
const difference = a.minus(b); // 55.56
const product = a.times(b); // 8,381.03
const quotient = a.div(b); // 1.81839...
// Power
const squared = a.pow(2); // 15,239.9025
// Square root
const root = a.sqrt(); // 11.11...
// Absolute value
const abs = new BigDecimal("-123.45").abs(); // 123.45
Comparison Methods
const x = new BigDecimal("10.5");
const y = new BigDecimal("10.5");
const z = new BigDecimal("9.9");
x.eq(y); // true (equal)
x.gt(z); // true (greater than)
x.gte(y); // true (greater than or equal)
x.lt(z); // false (less than)
x.lte(y); // true (less than or equal)
// Check for special values
x.isZero(); // false
x.isPositive(); // true
x.isNegative(); // false
x.isFinite(); // true
Rounding and Formatting
const value = new BigDecimal("123.456789");
// Get with specific decimal places
value.dp(2); // 123.46 (rounded)
value.dp(2, 1); // 123.45 (rounded down)
// Format as string
value.toString(); // "123.456789"
value.toFixed(2); // "123.46"
value.toExponential(2); // "1.23e+2"
value.toPrecision(5); // "123.46"
Working with Schema-Defined BigDecimal Fields
When you've defined a BigDecimal field in your schema:
type TokenPair {
id: ID!
name: String!
price: BigDecimal!
volume: BigDecimal!
}
You can use it in your handlers:
// In your event handler
context.TokenPair.set({
id: event.params.pairId,
name: event.params.name,
price: new BigDecimal(event.params.price),
volume: new BigDecimal("0"), // Start with zero volume
});
// Updating a field
const tokenPair = await context.TokenPair.get(pairId);
if (tokenPair) {
const newVolume = tokenPair.volume.plus(new BigDecimal(tradeAmount));
context.TokenPair.set({
...tokenPair,
volume: newVolume,
});
}
Example: Financial Calculation
function calculateFee(amount: BigDecimal, feeRate: BigDecimal): BigDecimal {
// Calculate fee with proper rounding
return amount.times(feeRate).dp(2);
}
const tradeAmount = new BigDecimal("1250.75");
const feeRate = new BigDecimal("0.0025"); // 0.25%
const fee = calculateFee(tradeAmount, feeRate); // 3.13
Best Practices for BigDecimal
-
Always use strings for initialization when precision matters:
// Preferred
const value = new BigDecimal("123.456789");
// May lose precision
const value = new BigDecimal(123.456789); -
Set precision explicitly when doing division:
// Set to 8 decimal places for crypto prices
const price = totalValue.div(tokenAmount).dp(8); -
Handle rounding appropriately for financial calculations:
// Round down (floor) for user-favorable calculations
const userReceives = amount.dp(2, 1); // ROUND_DOWN
// Round up (ceil) for protocol-favorable calculations
const protocolFee = amount.dp(2, 0); // ROUND_UP -
Compare with equals method instead of
==or===:// Correct
if (value.eq(new BigDecimal(0))) {
/* ... */
}
// Incorrect - compares object references
if (value === new BigDecimal(0)) {
/* ... */
} -
Chain operations carefully, remembering that each operation returns a new instance:
// Calculate (a + b) * c with proper precision
const result = a.plus(b).times(c).dp(8);
Enum Types
Enums allow fields to accept only a predefined set of values.
Example:
enum AccountType {
ADMIN
USER
}
type User {
id: ID!
balance: Int!
accountType: AccountType!
}
Enums translate to string unions (TypeScript/JavaScript) or polymorphic variants (ReScript):
TypeScript Example:
import { type Enum } from "envio";
let user = {
id: event.params.id,
balance: event.params.balance,
accountType: "USER" satisfies Enum<"AccountType">, // enum as string
};
ReScript Example:
let user: Types.userEntity = {
id: event.params.id,
balance: event.params.balance,
accountType: #USER, // polymorphic variant
};
Relationships: One-to-Many (@derivedFrom)
Define relationships between entities using the @derivedFrom directive, known as reverse lookups.
Example:
type NftCollection {
id: ID!
contractAddress: Bytes!
name: String!
symbol: String!
maxSupply: BigInt!
currentSupply: Int!
tokens: [Token!]! @derivedFrom(field: "collection")
}
type Token {
id: ID!
tokenId: BigInt!
collection: NftCollection!
owner: User!
}
- The
tokensfield inNftCollectionis a virtual field, populated automatically when querying the API. - Set relationships in your handlers by assigning
<field>_idwith the related entity'sid. For example, create or update aTokenentity withcollection_id: collectionId.
Field Indexing (@index)
Add an index to a field for optimized queries and loader performance:
type Token {
id: ID!
tokenId: BigInt!
collection: NftCollection!
owner: User! @index
}
- All
idfields and fields referenced via@derivedFromare indexed automatically.
Advanced: Precision and Scale (@config Directive)
Customize the precision and scale for BigInt and BigDecimal fields using @config.
Syntax:
BigInt(precision only):
amount: BigInt @config(precision: 76)
BigDecimal(precision and scale):
price: BigDecimal @config(precision: 10, scale: 2)
Example:
type Payment {
id: ID!
amount: BigInt @config(precision: 76)
price: BigDecimal @config(precision: 10, scale: 2)
}
This controls PostgreSQL storage allocation and numerical accuracy.
Detailed Example with Arrays
type AdvancedEntity {
exampleBigInt: BigInt @config(precision: 76)
exampleBigIntRequired: BigInt! @config(precision: 77)
exampleBigIntArray: [BigInt!] @config(precision: 78)
exampleBigIntArrayRequired: [BigInt!]! @config(precision: 79)
exampleBigDecimal: BigDecimal @config(precision: 10, scale: 5)
exampleBigDecimalRequired: BigDecimal! @config(precision: 12, scale: 4)
}
Documenting Entities, Fields, and Relationships
You can document your entities, fields, and relationships directly in schema.graphql using GraphQL string descriptions. These descriptions are exposed through the generated GraphQL API and appear in introspection, making your API self-documenting.
"""
A token transfer between two accounts
"""
type Transfer {
id: ID!
"The address the tokens were sent from"
from: String!
"The address the tokens were sent to"
to: String!
"The amount transferred, in wei"
value: BigInt!
}
Both single-line ("...") and multi-line ("""...""") descriptions are supported.
Only string descriptions are exposed in introspection. Hash (#) comments are ignored by the GraphQL parser and do not appear in the API. Descriptions on entities, fields, and relationships were added in HyperIndex v3.1.
Generating Types
Once you've defined your schema, run this command to generate these entity types that can be accessed in your event handlers:
pnpm envio codegen
Best Practices
- Use camelCase for field names (
latestGreeting,numberOfGreetings). - Keep entity and field names clear, descriptive, and intuitive.
You're now ready to define powerful schemas and efficiently query your indexed data with HyperIndex!