Query data

Every read in a package goes through pbtsdb and TanStack DB. You grab a collection handle with useStore, describe what you want with TanStack DB operators, and wrap the whole thing in useOrgLiveQuery so the query auto-scopes to the active organization.

Before you query

Your package must declare its collections (see Collections). Once the generator has wired them into MergedSchema, the name you return from registerCollections is the name you pass to useStore.

The pattern

import { useOrgLiveQuery, useStore } from '@tinycld/core/lib/pocketbase'
import { eq } from '@tanstack/db'

export default function ExampleList() {
    const [exampleCollection] = useStore('example')
    const { data } = useOrgLiveQuery((query, { orgId }) =>
        query
            .from({ example: exampleCollection })
            .where(({ example }) => eq(example.org, orgId))
            .orderBy(({ example }) => example.title, 'asc')
    )
    return <ItemList items={data ?? []} />
}

useStore accepts variadic collection names and returns a tuple. Destructure it positionally:

const [tagsCollection] = useStore('tags')
const [jobsCollection, addressesCollection] = useStore('jobs', 'addresses')

The query DSL

TanStack DB’s builder mirrors SQL. All operators are imported from @tanstack/db:

import { and, eq, gt, inArray, like, or } from '@tanstack/db'

query
    .from({ item: itemsCollection })
    .join(
        { user: usersCollection },
        ({ item, user }) => eq(item.owner, user.id),
        'left'
    )
    .where(({ item }) =>
        and(
            eq(item.org, orgId),
            or(eq(item.status, 'active'), gt(item.updated, lastWeek))
        )
    )
    .orderBy(({ item }) => item.updated, 'desc')
    .select(({ item, user }) => ({ ...item, ownerName: user.name }))

Use .select() when you want to compute a derived shape - it runs in the reactive pipeline, so downstream re-renders only happen when the computed value changes.

Org scoping

The only exceptions are bootstrap hooks that org-scoping itself depends on - @tinycld/core’s use-org-info, use-current-role, use-current-user-org - and genuinely user-level queries (theme preferences, notification settings). Packages should use useOrgLiveQuery everywhere.

Where queries live

Prefer inline queries in the screen or component that uses them. A hook per query makes the data flow invisible to future readers and encourages accidental duplication. Extract a shared hook only when the exact same query is called in three or more places - until then, the inline form is the honest one.

// good - data flow is visible at the point of use
export default function ExampleList() {
    const [exampleCollection] = useStore('example')
    const { data } = useOrgLiveQuery((query, { orgId }) =>
        query.from({ example: exampleCollection }).where(({ example }) => eq(example.org, orgId))
    )
    return <ItemList items={data ?? []} />
}

Common mistakes

For writes, see Mutate data.