Create & update records
Ponder's store API is inspired by the Prisma Client API (opens in a new tab). The store currently supports the following methods:
create
Insert a new records into the store.
Options
| name | type | |
|---|---|---|
| id | string | number | bigint | ID of the new record |
| data | TRecord | Data required for a new record |
Returns
Promise<TRecord>
Examples
import { p } from "@ponder/core";
export const schema = p.createSchema({
Token: p.createTable({
id: p.int(),
mintedBy: p.string(),
mintedAt: p.int(),
}),
});ponder.on("Blitmap:Mint", async ({ event, context }) => {
const { Token } = context.models;
const token = await Token.create({
id: event.params.tokenId,
data: {
mintedBy: event.params.to,
mintedAt: event.block.timestamp,
},
});
// { id: 7777, mintedBy: "0x7Df1...", mintedAt: 1679507353 }
});update
Update an record that already exists.
Options
| name | type | |
|---|---|---|
| id | string | number | bigint | ID of the updated record |
| data | Partial<TRecord> | Data to update |
| data (function) | (args: { current: TRecord }) => Partial<TRecord> | Function returning data to update |
Returns
Promise<TRecord>
Examples
import { p } from "@ponder/core";
export const schema = p.createSchema({
Token: p.createTable({
id: p.int(),
mintedBy: p.string(),
metadataUpdatedAt: p.int(),
}),
});ponder.on("Blitmap:MetadataUpdate", async ({ event, context }) => {
const { Token } = context.models;
const token = await Token.update({
id: event.params.tokenId,
data: {
metadataUpdatedAt: event.block.timestamp,
},
});
// { id: 7777, mintedBy: "0x1bA3...", updatedAt: 1679507354 }
});Update function
You can optionally pass a function to the data field that receives the current record as an argument and returns the update object. This is useful for updates that depend on the current record, like an incrementing count or balance.
import { p } from "@ponder/core";
export const schema = p.createSchema({
Account: p.createTable({
id: p.int(),
balance: p.bigint(),
}),
});ponder.on("ERC20:Transfer", async ({ event, context }) => {
const { Account } = context.models;
const recipient = await Account.update({
id: event.params.to,
data: ({ current }) => ({
balance: current.balance + event.params.value,
}),
});
// { id: "0x5D92..", balance: 11800000005n }
});upsert
Update a record if one already exists with the specified id, or create a new record.
Options
| name | type | |
|---|---|---|
| id | string | number | bigint | ID of the record to create or update |
| create | TRecord | Data required for a new record |
| update | Partial<TRecord> | Data to update |
| update (function) | (args: { current: TRecord }) => Partial<TRecord> | Function returning data to update |
Returns
Promise<TRecord>
Examples
Upsert can be useful for events like the ERC721 Transfer event, which is emitted when a token is minted and whenever a token is transferred.
import { p } from "@ponder/core";
export const schema = p.createSchema({
Token: p.createTable({
id: p.int(),
mintedBy: p.string().references("Account.id")
ownedBy: p.string().references("Account.id")
}),
});ponder.on("Blitmap:Transfer", async ({ event, context }) => {
const { Token } = context.models;
const token = await Token.upsert({
id: event.params.tokenId,
create: {
mintedBy: event.params.to,
ownedBy: event.params.to,
},
update: {
ownedBy: event.params.to,
},
});
// { id: 7777, mintedBy: "0x1bA3...", ownedBy: "0x7F4d..." }
});Update function
You can optionally pass a function to the update field that receives the current record as an argument and returns the update object. This is useful for updates that depend on the current record, like an incrementing count or balance.
import { p } from "@ponder/core";
export const schema = p.createSchema({
Token: p.createTable({
id: p.int(),
ownedBy: p.string().references("Account.id"),
transferCount: p.int(),
}),
});ponder.on("Blitmap:Transfer", async ({ event, context }) => {
const { Token } = context.models;
const token = await Token.upsert({
id: event.params.tokenId,
create: {
ownedBy: event.params.to,
transferCount: 0,
},
update: ({ current }) => ({
ownedBy: event.params.to,
transferCount: current.transferCount + 1,
}),
});
// { id: 7777, ownedBy: "0x7F4d...", transferCount: 1 }
});delete
delete deletes a record by id.
Options
| name | type | |
|---|---|---|
| id | string | number | bigint | ID of the record to delete |
Returns
Promise<boolean> (true if the record was deleted, false if it was not found)
Examples
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
const isDeleted = await Player.delete({ id: "Jim" });
// true
const jim = await Player.findUnique({ id: "Jim" });
// nullfindUnique
findUnique finds and returns a record by id.
Options
| name | type | |
|---|---|---|
| id | string | number | bigint | ID of the record to find and return |
Returns
Promise<TRecord | null>
Examples
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
const jim = await Player.findUnique({ id: "Jim" });
// { id: "Jim", age: 34 }
const sara = await Player.findUnique({ id: "Sara" });
// nullfindMany
findMany returns a list of records according to the filter, sort, and pagination options you provide. Note that findMany offers programmatic access to the functionality exposed by the autogenerated GraphQL API.
Options
| name | type | |
|---|---|---|
| where | WhereInput<TRecord> | undefined | Filter matching records to return |
| orderBy | OrderByInput<TRecord> | undefined | Sort applied to the list |
| skip | number | undefined | Number of records to skip (SQL OFFSET) |
| take | number | undefined | Number of records to take (SQL LIMIT) |
Returns
Promise<TRecord[]>
Examples
Filtering
Filter the result list by passing a where option containing a field name, filter condition, and value. The where option is typed according to the filter conditions available for each field.
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
await Player.create({ id: "Andrew", age: 19 });
await Player.create({ id: "Janet", age: 56 });
const players = await Player.findMany();
// [
// { id: "Jim", age: 34 },
// { id: "Andrew", age: 19 },
// { id: "Janet", age: 56 }
// ]
const players = await Player.findMany({
where: {
id: {
startsWith: "J",
},
},
});
// [
// { id: "Jim", age: 34 },
// { id: "Janet", age: 56 }
// ]If you provide multiple filters, they will be combined with a logical AND.
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
await Player.create({ id: "Andrew", age: 19 });
await Player.create({ id: "Janet", age: 56 });
const players = await Player.findMany({
where: {
id: { contains: "e" }
age: { gt: 30 }
}
});
// [
// { id: "Janet", age: 56 }
// ]Sorting
Sort the result list by passing an orderBy option containing a field name and sort direction ("asc" or "desc").
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
await Player.create({ id: "Andrew", age: 19 });
await Player.create({ id: "Janet", age: 56 });
const players = await Player.findMany({
orderBy: {
age: "asc",
},
});
// [
// { id: "Andrew", age: 19 },
// { id: "Jim", age: 34 },
// { id: "Janet", age: 56 }
// ]Pagination
Paginate through the result list using the skip and take options.
Avoid using findMany to return result lists that require pagination. (If you
need this, please reach out so we can better support your use case.)
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
await Player.create({ id: "Andrew", age: 19 });
await Player.create({ id: "Janet", age: 56 });
await Player.create({ id: "Polly", age: 29 });
const players = await Player.findMany({
orderBy: { age: "desc" },
skip: 1,
take: 2,
});
// [
// { id: "Jim", age: 34 },
// { id: "Polly", age: 29 }
// ]createMany
createMany inserts multiple records into the store in a single operation. It returns a list of the created records.
Options
| name | type | |
|---|---|---|
| data | TRecord[] | List of records to create |
Returns
Promise<TRecord[]>
Examples
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.createMany({
data: [
{ id: "Jim", age: 34 },
{ id: "Andrew", age: 19 },
{ id: "Janet", age: 56 },
],
});
const players = await Player.findMany();
// [
// { id: "Jim", age: 34 },
// { id: "Andrew", age: 19 },
// { id: "Janet", age: 56 }
// ]updateMany
updateMany updates multiple records in a single operation using the same update logic. Like the update method, updateMany also optionally accepts an update function.
Options
| name | type | |
|---|---|---|
| where | WhereInput<TRecord> | Filter matching records to be updated |
| data | Partial<TRecord> | Data to update |
| data (function) | (args: { current: TRecord }) => Partial<TRecord> | Function returning data to update |
Returns
Promise<TRecord[]>
Examples
import { p } from "@ponder/core";
export const schema = p.createSchema({
Player: p.createTable({
id: p.string(),
age: p.int(),
}),
});await Player.create({ id: "Jim", age: 34 });
await Player.create({ id: "Andrew", age: 19 });
await Player.create({ id: "Janet", age: 56 });
await Player.updateMany({
where: {
id: {
startsWith: "J",
},
},
data: {
age: 50,
},
});
const players = await Player.findMany();
// [
// { id: "Jim", age: 50 },
// { id: "Andrew", age: 19 },
// { id: "Janet", age: 50 }
// ]