Create & update records

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

nametype
idstring | number | bigintID of the new record
dataTRecordData required for a new record

Returns

Promise<TRecord>

Examples

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Token: p.createTable({
    id: p.int(),
    mintedBy: p.string(),
    mintedAt: p.int(),
  }),
});
src/index.ts
ponder.on("Blitmap:Mint", async ({ event, context }) => {
  const { Token } = context.db;
 
  const token = await Token.create({
    id: event.args.tokenId,
    data: {
      mintedBy: event.args.to,
      mintedAt: event.block.timestamp,
    },
  });
  // { id: 7777, mintedBy: "0x7Df1...", mintedAt: 1679507353 }
});

update

Update an record that already exists.

Options

nametype
idstring | number | bigintID of the updated record
dataPartial<TRecord>Data to update
data (function)(args: { current: TRecord }) => Partial<TRecord>Function returning data to update

Returns

Promise<TRecord>

Examples

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Token: p.createTable({
    id: p.int(),
    mintedBy: p.string(),
    metadataUpdatedAt: p.int(),
  }),
});
src/index.ts
ponder.on("Blitmap:MetadataUpdate", async ({ event, context }) => {
  const { Token } = context.db;
 
  const token = await Token.update({
    id: event.args.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.

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Account: p.createTable({
    id: p.int(),
    balance: p.bigint(),
  }),
});
src/index.ts
ponder.on("ERC20:Transfer", async ({ event, context }) => {
  const { Account } = context.db;
 
  const recipient = await Account.update({
    id: event.args.to,
    data: ({ current }) => ({
      balance: current.balance + event.args.value,
    }),
  });
  // { id: "0x5D92..", balance: 11800000005n }
});

upsert

Update a record if one already exists with the specified id, or create a new record.

Options

nametype
idstring | number | bigintID of the record to create or update
createTRecordData required for a new record
updatePartial<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.

ponder.schema.ts
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")
  }),
});
src/index.ts
ponder.on("Blitmap:Transfer", async ({ event, context }) => {
  const { Token } = context.db;
 
  const token = await Token.upsert({
    id: event.args.tokenId,
    create: {
      mintedBy: event.args.to,
      ownedBy: event.args.to,
    },
    update: {
      ownedBy: event.args.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.

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Token: p.createTable({
    id: p.int(),
    ownedBy: p.string().references("Account.id"),
    transferCount: p.int(),
  }),
});
src/index.ts
ponder.on("Blitmap:Transfer", async ({ event, context }) => {
  const { Token } = context.db;
 
  const token = await Token.upsert({
    id: event.args.tokenId,
    create: {
      ownedBy: event.args.to,
      transferCount: 0,
    },
    update: ({ current }) => ({
      ownedBy: event.args.to,
      transferCount: current.transferCount + 1,
    }),
  });
  // { id: 7777, ownedBy: "0x7F4d...", transferCount: 1 }
});

delete

delete deletes a record by id.

Options

nametype
idstring | number | bigintID of the record to delete

Returns

Promise<boolean> (true if the record was deleted, false if it was not found)

Examples

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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

nametype
idstring | number | bigintID of the record to find and return

Returns

Promise<TRecord | null>

Examples

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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

nametype
whereWhereInput<TRecord> | undefinedFilter matching records to return
orderByOrderByInput<TRecord> | undefinedSort applied to the list
skipnumber | undefinedNumber of records to skip (SQL OFFSET)
takenumber | undefinedNumber 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.

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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.

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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").

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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.)

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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

nametype
dataTRecord[]List of records to create

Returns

Promise<TRecord[]>

Examples

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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

nametype
whereWhereInput<TRecord>Filter matching records to be updated
dataPartial<TRecord>Data to update
data (function)(args: { current: TRecord }) => Partial<TRecord>Function returning data to update

Returns

Promise<TRecord[]>

Examples

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Player: p.createTable({
    id: p.string(),
    age: p.int(),
  }),
});
src/index.ts
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 }
// ]