> ## Documentation Index
> Fetch the complete documentation index at: https://www.orionjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Paginated Queries

> Creating paginated table queries with tRPC

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

```typescript theme={null}
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:

```typescript theme={null}
interface PaginationParams {
  skip: number                    // Calculated from page and limit
  limit: number                   // Items per page
  sort: {[key: string]: 1 | -1}   // Sort object for MongoDB
}
```

Example:

```typescript theme={null}
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.

```typescript theme={null}
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.

```typescript theme={null}
const result = await caller.listUsers.getCount({
  params: {search: 'john'},
})
// Returns: { totalCount: number }
```

### getDescription

Returns the sorting configuration. Takes no input.

```typescript theme={null}
const result = await caller.listUsers.getDescription()
// Returns: { allowedSorts: string[], defaultSortBy?: string, defaultSortType?: 'asc' | 'desc' }
```

## Options

| Option            | Type            | Description                                                                   |
| ----------------- | --------------- | ----------------------------------------------------------------------------- |
| `params`          | Schema          | Custom input params schema (optional)                                         |
| `returns`         | Schema          | Output item schema for cleaning (optional)                                    |
| `getItems`        | Function        | `(paginationParams, params, viewer) => items[]` - Returns the paginated items |
| `getCount`        | Function        | `(params, viewer) => number` - Returns the total count for the query          |
| `allowedSorts`    | string\[]       | List of allowed sort fields (optional)                                        |
| `defaultSortBy`   | string          | Default sort field (optional)                                                 |
| `defaultSortType` | 'asc' \| 'desc' | Default sort direction (optional)                                             |
| `defaultLimit`    | number          | Default page size (default: 20)                                               |
| `maxLimit`        | number          | Maximum allowed page size (default: 200)                                      |

## Pagination Fields

The following fields are available in the `getItems` input:

| Field      | Type            | Default | Description                                       |
| ---------- | --------------- | ------- | ------------------------------------------------- |
| `page`     | integer         | 1       | Page number (min: 1)                              |
| `limit`    | integer         | 20      | Items per page (min: 0, max: 200)                 |
| `sortBy`   | string          | -       | 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:

```typescript theme={null}
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`:

```typescript theme={null}
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:

```typescript theme={null}
@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

```typescript theme={null}
// 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)
```
