Skip to main content

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 id field, using one of these scalar types:
    • ID!, String!, Int!, Bytes!, or BigInt!

Scalar Types

Scalar types represent basic data types and map directly to JavaScript, TypeScript, or ReScript types.

GraphQL ScalarDescriptionJavaScript/TypeScriptReScript
IDUnique identifierstringstring
StringUTF-8 character sequencestringstring
IntSigned 32-bit integernumberint
FloatSigned floating-point numbernumberfloat
Booleantrue or falsebooleanbool
BytesUTF-8 character sequence (hex prefixed 0x)stringstring
BigIntSigned integer (int256 in Solidity)bigintbigint
BigDecimalArbitrary-size floating-pointBigDecimal (imported)BigDecimal.t
TimestampTimestamp with timezoneDateJs.Date.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 "../generated/src/BigDecimal.js";

// 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);

// Special values
const zero = BigDecimal.ZERO;
const one = BigDecimal.ONE;

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

  1. Always use strings for initialization when precision matters:

    // Preferred
    const value = new BigDecimal("123.456789");

    // May lose precision
    const value = new BigDecimal(123.456789);
  2. Set precision explicitly when doing division:

    // Set to 8 decimal places for crypto prices
    const price = totalValue.div(tokenAmount).dp(8);
  3. 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
  4. Compare with equals method instead of == or ===:

    // Correct
    if (value.eq(BigDecimal.ZERO)) {
    /* ... */
    }

    // Incorrect - compares object references
    if (value === BigDecimal.ZERO) {
    /* ... */
    }
  5. 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 { AccountType } from "../generated/src/Enums.gen";

let user = {
id: event.params.id,
balance: event.params.balance,
accountType: "USER", // 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 tokens field in NftCollection is a virtual field, populated automatically when querying the API.
  • Set relationships by referencing the related entity's id.

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 id fields and fields referenced via @derivedFrom are 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)
}

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!