Selectors

Selectors are computed, reactive values derived from your collections. They track which collections they use and refresh when those collections change.

Defining selectors

Via db.select():

export const userPostCounts = db.select(async (ctx) => {
  const [users, posts] = await Promise.all([ctx.users.all(), ctx.posts.all()])
  return users.map((user) => ({
    userId: user.id,
    name: user.name,
    postCount: posts.filter((post) => post.userId === user.id).length,
  }))
})

Via Selector.create():

import { Selector } from "async-idb-orm"
import type * as schema from "./collections"
import type * as relations from "./relations"

export const allUserNames = Selector.create<typeof schema, typeof relations>().as(async (ctx) => {
  return (await ctx.users.all()).map((user) => user.name)
})

Register selectors made via Selector.create() when creating the DB:

export const db = idb("my-app", {
  schema,
  relations,
  selectors: { allUserNames },
  version: 1,
})

Using selectors

Promise-based:

const userNames = await db.selectors.allUserNames.get()
const postCounts = await userPostCounts.get()

Reactive subscriptions:

const unsubscribe = db.selectors.allUserNames.subscribe((names) => {
  console.log("User names updated:", names)
})
// later: unsubscribe()

When any collection the selector touched is modified (create, update, delete, clear), the selector recomputes and notifies subscribers.

Behaviour

UI integration

Use subscribe in your framework’s effect/lifecycle and store the result in local state:

function useUserNames() {
  const [userNames, setUserNames] = useState<string[]>([])
  useEffect(() => {
    const unsub = db.selectors.allUserNames.subscribe(setUserNames)
    return unsub
  }, [])
  return userNames
}

Return types are inferred from the selector function.