Skip to content

Querying the database ​

The Canvas application object exposes its database at app.db:

ts
const app = await Canvas.initialize(...)
await app.db.get("posts", postId)

You can query the database outside action handlers by using app.db.get, app.db.query, or useLiveQuery for live-updating data.

Getting records ​

All database models have a primary key, defined in the model schema using the "primary" type. You can look up individual records by primary key using app.db.get.

ts
const app = await Canvas.initialize({
  contract: {
    topic: "com.example.my-app",
    models: {
      posts: {
        id: "primary",
        user: "string",
        content: "string",
        updated_at: "integer",
      },
    },
    actions: { ... }
  },
})

const { id: idA } = await app.actions.createPost({ content: "foo" }) // 08arkku017tl90n9dptlkkd62vooji11
const { id: idB } = await app.actions.createPost({ content: "bar" }) // 0cfag48t05mags2lhdt9idn3cbpubl1e
const { id: idC } = await app.actions.createPost({ content: "baz" }) // 0j77pkoundspspv1dgkppvceduu8s2t1

await app.db.get("posts", idA)
// {
//   id: '08arkku017tl90n9dptlkkd62vooji11',
//   user: 'did:pkh:eip155:1:0x8A876c44064b77b36Cb3e0524DeeC1416858bDE6',
//   content: 'foo',
//   updated_at: 1698344798345
// }

Querying records ​

The app.db.query method supports more expressive queries, including select, where, orderBy, limit, and offset clauses. json fields cannot be referenced in where or orderBy clauses.

ts
type QueryParams = {
  select?: Record<string, boolean>
  where?: WhereCondition
  orderBy?: Record<string, "asc" | "desc">
  limit?: number
  offset?: number
}

type WhereCondition = Record<string, PropertyValue | NotExpression | RangeExpression>
type NotExpression = { neq: PropertyValue }
type RangeExpression = { gt?: PrimitiveValue; gte?: PrimitiveValue; lt?: PrimitiveValue; lte?: PrimitiveValue }

type PrimitiveValue = null | string | number | Uint8Array
type PropertyValue = null | string | number | Uint8Array | string[]
ts
// Get all posts
const results = await app.db.query("posts", {})

// [
//   {
//     id: '08arkku017tl90n9dptlkkd62vooji11',
//     user: 'did:pkh:eip155:1:0x8A876c44064b77b36Cb3e0524DeeC1416858bDE6',
//     content: 'foo',
//     updated_at: 1698344798345
//   },
//   {
//     id: '0cfag48t05mags2lhdt9idn3cbpubl1e',
//     user: 'did:pkh:eip155:1:0x8A876c44064b77b36Cb3e0524DeeC1416858bDE6',
//     content: 'bar',
//     updated_at: 1698344798363
//   },
//   {
//     id: '0j77pkoundspspv1dgkppvceduu8s2t1',
//     user: 'did:pkh:eip155:1:0x8A876c44064b77b36Cb3e0524DeeC1416858bDE6',
//     content: 'baz',
//     updated_at: 1698344798366
//   }
// ]
ts
// Get all posts, sorted by timestamp
const results = await app.db.query("posts", {
  select: { id: true, content: true },
  orderBy: { updated_at: "desc" },
})

// [
//   { id: '0j77pkoundspspv1dgkppvceduu8s2t1', content: 'baz' },
//   { id: '0cfag48t05mags2lhdt9idn3cbpubl1e', content: 'bar' },
//   { id: '08arkku017tl90n9dptlkkd62vooji11', content: 'foo' },
// ]
ts
// Get all posts, filtering by a range expression on `content`
const results = await app.db.query("posts", {
  select: { id: true, content: true },
  where: { content: { lte: "eee" } },
})

// [
//   { id: '0cfag48t05mags2lhdt9idn3cbpubl1e', content: 'bar' },
//   { id: '0j77pkoundspspv1dgkppvceduu8s2t1', content: 'baz' },
// ]

Live queries in React ​

You can also subscribe to queries in the browser using the useLiveQuery hook exported from @canvas-js/hooks.

tsx
import { useCanvas, useLiveQuery } from "@canvas-js/hooks"

const contract = {
  topic: "com.example.my-app",
  models: { ... },
  actions: {
    async createPost(db, args, {}) { ... }
    async deletePost(db, args, {}) { ... }
  }
}

const MyComponent = (props: {}) => {
  const { app, error } = useCanvas({ contract })

  const posts = useLiveQuery(app, "posts", { limit: 10, orderBy: { updated_at: "desc" } })
  return (
    <div>
      {posts.map((post) => <div id={post.id}>{post.content}</div>)}
    </div>
  )
}