Store interface
When working with Subsquid SDK and building on the SubstrateProcessor, the Store is a very important concept one. As briefly explained in the Key Concept page dedicated to the Processor, it serves as an interface for the data-persistence layer, often represented by a database.
The Store interface we implemented is an extension of the EntityManager from the TypeORM library. It is a dependency of the project, so it should be installed with the rest of them, but to manually install it, simply run:
1
npm install typeorm
Copied!
In the context of the SubstrateProcessor, there are a few noteworthy functions exposed by the Store, which we'll focus on during this guide.
The Store interface is usually passed as part of the EventHandlerContext, ExtrinsicHandlerContext or BlockHandlerContext, depending on the handler on the receiving end:
1
export interface EventHandlerContext {
2
store: Store
3
block: SubstrateBlock
4
event: SubstrateEvent
5
extrinsic?: SubstrateExtrinsic
6
/**
7
* Not yet public description of chain metadata
8
* @internal
9
*/
10
_chain: Chain
11
}
Copied!
So when defining an Event Handler, it's possible to use all the functions exposed by the interface. Here is a summary of the most important ones:

Finding objects

The Store interface exposes various methods for finding objects, here's a few, defined directly as part of EntityManager parent interface:
1
export declare class EntityManager {
2
// ...
3
4
// Finds entities that match given options.
5
find<Entity>(entityClass: EntityTarget<Entity>, options?: FindManyOptions<Entity>): Promise<Entity[]>;
6
// Finds entities that match given conditions.
7
find<Entity>(entityClass: EntityTarget<Entity>, conditions?: FindConditions<Entity>): Promise<Entity[]>;
8
}
Copied!
Where options can be an object containing options arguments like: select, where, relations, join, order, cache. An example of usage might be:
1
let scalar = await em.find(Scalar, {
2
order: {
3
id: 'ASC'
4
}
5
})
Copied!
Other methods include:
1
export declare class EntityManager {
2
// ...
3
4
// Finds entities with ids.
5
// Optionally find options can be applied.
6
findByIds<Entity>(entityClass: EntityTarget<Entity>, ids: any[], options?: FindManyOptions<Entity>): Promise<Entity[]>;
7
// Finds entities with ids.
8
// Optionally conditions can be applied.
9
findByIds<Entity>(entityClass: EntityTarget<Entity>, ids: any[], conditions?: FindConditions<Entity>): Promise<Entity[]>;
10
}
Copied!
And:
1
export declare class EntityManager {
2
// ...
3
// Finds first entity that matches given find options.
4
findOne<Entity>(entityClass: EntityTarget<Entity>, id?: string | number | Date | ObjectID, options?: FindOneOptions<Entity>): Promise<Entity | undefined>;
5
6
// Finds first entity that matches given find options.
7
findOne<Entity>(entityClass: EntityTarget<Entity>, options?: FindOneOptions<Entity>): Promise<Entity | undefined>;
8
9
// Finds first entity that matches given conditions.
10
findOne<Entity>(entityClass: EntityTarget<Entity>, conditions?: FindConditions<Entity>, options?: FindOneOptions<Entity>): Promise<Entity | undefined>;
11
}
Copied!

Persisting objects

In order to persist objects in the database, the Store interface has the save method, which has various signatures available:
1
export declare class EntityManager {
2
3
// ...
4
5
// Saves all given entities in the database.
6
// If entities do not exist in the database then inserts, otherwise updates.
7
save<Entity>(entities: Entity[], options?: SaveOptions): Promise<Entity[]>;
8
9
// Saves all given entities in the database.
10
// If entities do not exist in the database then inserts, otherwise updates.
11
save<Entity>(entity: Entity, options?: SaveOptions): Promise<Entity>;
12
13
// Saves all given entities in the database.
14
// If entities do not exist in the database then inserts, otherwise updates.
15
save<Entity, T extends DeepPartial<Entity>>(targetOrEntity: EntityTarget<Entity>, entities: T[], options: SaveOptions & {
16
reload: false;
17
}): Promise<T[]>;
18
19
// Saves all given entities in the database.
20
// If entities do not exist in the database then inserts, otherwise updates.
21
save<Entity, T extends DeepPartial<Entity>>(targetOrEntity: EntityTarget<Entity>, entities: T[], options?: SaveOptions): Promise<(T & Entity)[]>;
22
23
// Saves a given entity in the database.
24
// If entity does not exist in the database then inserts, otherwise updates.
25
save<Entity, T extends DeepPartial<Entity>>(targetOrEntity: EntityTarget<Entity>, entity: T, options: SaveOptions & {
26
reload: false;
27
}): Promise<T>;
28
29
// Saves a given entity in the database.
30
// If entity does not exist in the database then inserts, otherwise updates.
31
save<Entity, T extends DeepPartial<Entity>>(targetOrEntity: EntityTarget<Entity>, entity: T, options?: SaveOptions): Promise<T & Entity>;
32
}
Copied!
A typical usage is, in the body of an Event or Extrinsic Handler function, to save an Entity in the database:
1
processor.addEventHandler('balances.Transfer', async ctx => {
2
let transfer = getTransferEvent(ctx)
3
let tip = ctx.extrinsic?.tip || 0n
4
let from = ss58.codec('kusama').encode(transfer.from)
5
let to = ss58.codec('kusama').encode(transfer.to)
6
7
let fromAcc = await getOrCreate(ctx.store, Account, from)
8
fromAcc.balance = fromAcc.balance || 0n
9
fromAcc.balance -= transfer.amount
10
fromAcc.balance -= tip
11
await ctx.store.save(fromAcc)
12
13
const toAcc = await getOrCreate(ctx.store, Account, to)
14
toAcc.balance = toAcc.balance || 0n
15
toAcc.balance += transfer.amount
16
await ctx.store.save(toAcc)
17
18
await ctx.store.save(new HistoricalBalance({
19
id: ctx.event.id + '-to',
20
account: fromAcc,
21
balance: fromAcc.balance,
22
date: new Date(ctx.block.timestamp)
23
}))
24
25
await ctx.store.save(new HistoricalBalance({
26
id: ctx.event.id + '-from',
27
account: toAcc,
28
balance: toAcc.balance,
29
date: new Date(ctx.block.timestamp)
30
}))
31
})
Copied!

Ad-hoc queries

When the offered methods aren't enough, it is still possible to run ad-hoc queries through the Store interface, thanks to the query method:
1
export declare class EntityManager {
2
// ...
3
4
// Executes raw SQL query and returns raw database results.
5
query(query: string, parameters?: any[]): Promise<any>;
6
}
Copied!
Here are a couple of examples:
1
processor.addEventHandler("balances.Transfer", async (ctx) => {
2
3
let schema = `"${processor}_status"`
4
await ctx.store.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`)
5
await ctx.store.query(`
6
CREATE TABLE IF NOT EXISTS ${schema}."status" (
7
id int primary key,
8
height int not null
9
)
10
`)
11
await ctx.store.query(`INSERT INTO ${schema}.status (id, height) VALUES (0, -1)`)
12
let height = await ctx.store.query('SELECT height FROM status WHERE id = 0');
13
});
Copied!

Bulk updates

Examples of usage of ad-hoc queries include the possibility to perform bulk-updates:
1
await ctx.store.query(`UPDATE ${schema}.status SET height = $1 WHERE height < $1`, [blockNumber]
Copied!