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 default 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 default 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 default 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 default 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 default 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 default 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" });
// null
findUnique
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 default 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" });
// null
findMany
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 default 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 default 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 default 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 default 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 default 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 default 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 }
// ]