Files
supabase/apps/studio/data/database-queues/database-queue-messages-infinite-query.ts
T
oniani1 6b57d5f57a fix(studio): fix infinite pagination loop in queue messages (#44292)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix

## What is the current behavior?

The `getNextPageParam` function in
`database-queue-messages-infinite-query.ts` uses `<=` instead of `>=`,
which means pagination never stops. Since the result length is always
less than or equal to the page size, `hasNextPage` is always `true`,
causing infinite API requests when viewing queue messages in Studio.

Resolves #44291

## What is the new behavior?

Uses `>=` to correctly detect when there are more pages, consistent with
every other infinite query in the codebase:

- `database-cron-jobs-infinite-query.ts` uses `>=`
- `users-infinite-query.ts` uses `>=`
- `database-cron-jobs-runs-infinite-query.ts` uses `< PAGE_SIZE` to
return `undefined` (equivalent logic)

## Additional context

One character change: `<=` to `>=` on line 112.
2026-03-31 09:03:39 +00:00

118 lines
3.7 KiB
TypeScript

import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'
import dayjs from 'dayjs'
import { last } from 'lodash'
import { isQueueNameValid } from 'components/interfaces/Integrations/Queues/Queues.utils'
import { QUEUE_MESSAGE_TYPE } from 'components/interfaces/Integrations/Queues/SingleQueue/Queue.utils'
import { executeSql } from 'data/sql/execute-sql-query'
import { DATE_FORMAT } from 'lib/constants'
import type { ResponseError, UseCustomInfiniteQueryOptions } from 'types'
import { databaseQueuesKeys } from './keys'
export type DatabaseQueueVariables = {
projectRef?: string
connectionString?: string | null
queueName: string
status: QUEUE_MESSAGE_TYPE[]
}
export type PostgresQueueMessage = {
msg_id: number
read_ct: number
enqueued_at: string
archived_at: string
vt: Date
message: Record<string, never>
}
export const QUEUE_MESSAGES_PAGE_SIZE = 30
export async function getDatabaseQueue({
projectRef,
connectionString,
queueName,
afterTimestamp,
status,
}: DatabaseQueueVariables & { afterTimestamp: string | undefined }) {
if (!projectRef) throw new Error('Project ref is required')
if (!isQueueNameValid(queueName)) {
throw new Error(
'Invalid queue name: must contain only alphanumeric characters, underscores, and hyphens'
)
}
if (status.length === 0) {
return []
}
// handles when scheduled and available are deselected
let queueQuery = ``
if (status.includes('available') && status.includes('scheduled')) {
queueQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM "pgmq"."q_${queueName}"`
} else if (status.includes('available') && !status.includes('scheduled')) {
queueQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM "pgmq"."q_${queueName}" WHERE vt < '${dayjs(new Date()).format(DATE_FORMAT)}'`
} else if (!status.includes('available') && status.includes('scheduled')) {
queueQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, NULL as archived_at FROM "pgmq"."q_${queueName}" WHERE vt > '${dayjs(new Date()).format(DATE_FORMAT)}'`
}
let archivedQuery = ``
if (status.includes('archived')) {
archivedQuery = `SELECT msg_id, enqueued_at, read_ct, vt, message, archived_at FROM "pgmq"."a_${queueName}"`
}
let query = `SELECT
*
FROM
(
${[queueQuery, archivedQuery].filter(Boolean).join(' UNION ALL ')}
) AS combined`
if (afterTimestamp) {
query += ` WHERE enqueued_at > '${afterTimestamp}'`
}
const { result } = await executeSql({
projectRef,
connectionString,
sql: `${query} order by enqueued_at LIMIT ${QUEUE_MESSAGES_PAGE_SIZE}`,
})
return result as DatabaseQueueData
}
export type DatabaseQueueData = PostgresQueueMessage[]
export type DatabaseQueueError = ResponseError
export const useQueueMessagesInfiniteQuery = <TData = DatabaseQueueData>(
{ projectRef, connectionString, queueName, status }: DatabaseQueueVariables,
{
enabled = true,
...options
}: UseCustomInfiniteQueryOptions<
DatabaseQueueData,
DatabaseQueueError,
InfiniteData<TData>,
readonly unknown[],
string | undefined
> = {}
) =>
useInfiniteQuery({
queryKey: databaseQueuesKeys.getMessagesInfinite(projectRef, queueName, { status }),
queryFn: ({ pageParam }) => {
return getDatabaseQueue({
projectRef,
connectionString,
queueName,
afterTimestamp: pageParam,
status,
})
},
staleTime: 0,
enabled: enabled && typeof projectRef !== 'undefined',
initialPageParam: undefined,
getNextPageParam(lastPage) {
const hasNextPage = lastPage.length >= QUEUE_MESSAGES_PAGE_SIZE
if (!hasNextPage) return undefined
return last(lastPage)?.enqueued_at
},
...options,
})