Skip to main content
The createTPaginatedQuery function creates a sub-router with three separate procedures for paginated tables: getItems, getCount, and getDescription. Each procedure has its own typed input and output.

Basic Usage

import {Procedures, TPaginatedQuery, createTPaginatedQuery} from '@orion-js/trpc'
import {Inject} from '@orion-js/services'
import {UsersRepo} from 'app/users/repos/UsersRepo'

@Procedures()
export class UserProcedures {
  @Inject(() => UsersRepo)
  private usersRepo: UsersRepo

  @TPaginatedQuery()
  listUsers = createTPaginatedQuery({
    params: {
      search: {type: String, optional: true},
    },
    allowedSorts: ['name', 'createdAt'],
    defaultSortBy: 'createdAt',
    defaultSortType: 'desc',
    getItems: async ({skip, limit, sort}, {search}, viewer) => {
      const query = search ? {name: {$regex: search, $options: 'i'}} : {}
      return this.usersRepo.find(query).skip(skip).limit(limit).sort(sort).toArray()
    },
    getCount: async ({search}, viewer) => {
      const query = search ? {name: {$regex: search, $options: 'i'}} : {}
      return this.usersRepo.countDocuments(query)
    },
  })
}

Pagination Parameters

The getItems function receives pagination parameters as its first argument, ready to use with MongoDB:
interface PaginationParams {
  skip: number                    // Calculated from page and limit
  limit: number                   // Items per page
  sort: {[key: string]: 1 | -1}   // Sort object for MongoDB
}
Example:
getItems: async ({skip, limit, sort}, params, viewer) => {
  // Use directly with MongoDB
  return collection.find(query).skip(skip).limit(limit).sort(sort).toArray()
}

Procedures

Each createTPaginatedQuery returns three separate tRPC procedures, accessible as nested routes:

getItems

Returns the paginated items. Input includes pagination fields and custom params.
const result = await caller.listUsers.getItems({
  page: 1,
  limit: 10,
  sortBy: 'name',
  sortType: 'asc',
  params: {search: 'john'},
})
// Returns: { items: User[] }

getCount

Returns the total count (for calculating total pages on the client). Input includes only custom params.
const result = await caller.listUsers.getCount({
  params: {search: 'john'},
})
// Returns: { totalCount: number }

getDescription

Returns the sorting configuration. Takes no input.
const result = await caller.listUsers.getDescription()
// Returns: { allowedSorts: string[], defaultSortBy?: string, defaultSortType?: 'asc' | 'desc' }

Options

OptionTypeDescription
paramsSchemaCustom input params schema (optional)
returnsSchemaOutput item schema for cleaning (optional)
getItemsFunction(paginationParams, params, viewer) => items[] - Returns the paginated items
getCountFunction(params, viewer) => number - Returns the total count for the query
allowedSortsstring[]List of allowed sort fields (optional)
defaultSortBystringDefault sort field (optional)
defaultSortType’asc’ | ‘desc’Default sort direction (optional)
defaultLimitnumberDefault page size (default: 20)
maxLimitnumberMaximum allowed page size (default: 200)

Pagination Fields

The following fields are available in the getItems input:
FieldTypeDefaultDescription
pageinteger1Page number (min: 1)
limitinteger20Items per page (min: 0, max: 200)
sortBystring-Sort field (only if allowedSorts is configured)
sortType’asc’ | ‘desc’-Sort direction

Type Inference

Item types are automatically inferred from the getItems return type - no manual type annotations needed:
interface User {
  id: string
  name: string
  email: string
}

@TPaginatedQuery()
listUsers = createTPaginatedQuery({
  getItems: async ({skip, limit, sort}) => {
    // Return type User[] is inferred automatically
    const users: User[] = await this.usersRepo.find({}).skip(skip).limit(limit).sort(sort).toArray()
    return users
  },
  getCount: async () => this.usersRepo.countDocuments({}),
})
For proper type inference with InferRouterOutputs, use the custom InferRouterOutputs type from @orion-js/trpc:
import {InferRouterOutputs} from '@orion-js/trpc'

type RouterOutputs = InferRouterOutputs<typeof router>
type ListUsersOutput = RouterOutputs['listUsers']
// ListUsersOutput.getItems.items is correctly typed as User[]
// ListUsersOutput.getCount.totalCount is number
// ListUsersOutput.getDescription.allowedSorts is string[]

Schema Cleaning

If you provide a returns schema, items will be cleaned/filtered according to that schema:
@TPaginatedQuery()
listUsers = createTPaginatedQuery({
  returns: {id: {type: 'ID'}, name: {type: String}},
  getItems: async ({skip, limit, sort}) => {
    return this.usersRepo.find({}).skip(skip).limit(limit).sort(sort).toArray()
  },
  getCount: async () => this.usersRepo.countDocuments({}),
})
// Only 'id' and 'name' fields will be returned, extra fields are stripped

Client Usage Example

// In your paginated table component
const [page, setPage] = useState(1)
const [sortBy, setSortBy] = useState<string>()
const [sortType, setSortType] = useState<'asc' | 'desc'>()

// Fetch items
const {data: itemsResult} = trpc.listUsers.getItems.useQuery({
  page,
  limit: 10,
  sortBy,
  sortType,
  params: {},
})

// Fetch count for pagination
const {data: countResult} = trpc.listUsers.getCount.useQuery({
  params: {},
})

// Fetch description for sort options
const {data: descriptionResult} = trpc.listUsers.getDescription.useQuery()

// Access data directly - each procedure has its own specific return type
const items = itemsResult?.items ?? []
const totalCount = countResult?.totalCount ?? 0
const allowedSorts = descriptionResult?.allowedSorts ?? []

const totalPages = Math.ceil(totalCount / 10)