Files
supabase/apps/ui-library/public/r/infinite-query-composable.json
cadfc8731c docs: realtime chat/infinite query vue&nuxt (#44426)
## 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?

This PR is the final PR for Supabase UI Vue&Nuxt with the Realtime Chat
and Infinite Query.

## Additional context

Initiative by Terry


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Realtime Chat now available for Vue and Nuxt.js frameworks with full
documentation and composables
  * Added Infinite Query composable for Vue with comprehensive guides

* **Documentation**
  * New Realtime Chat documentation pages for Vue and Nuxt.js
  * New Infinite Query documentation for Vue
  * Updated framework support in documentation navigation

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Terry Sutton <saltcod@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:33:21 -02:30

19 lines
5.1 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "infinite-query-composable",
"type": "registry:component",
"title": "Infinite Query for Vue and Supabase",
"description": "Vue Composable for infinite lists, fetching data from Supabase.",
"dependencies": [
"@supabase/supabase-js@latest",
"@supabase/postgrest-js@*"
],
"files": [
{
"path": "registry/default/infinite-query/vue/composables/useInfiniteQuery.ts",
"content": "import { PostgrestQueryBuilder, type PostgrestClientOptions } from '@supabase/postgrest-js'\nimport { type SupabaseClient } from '@supabase/supabase-js'\nimport { computed, onMounted, reactive, toRefs, watch } from 'vue'\n\n// @ts-ignore\nimport { createClient } from '@/lib/supabase/client'\n\nconst supabase = createClient()\n\ntype SupabaseClientType = typeof supabase\n\ntype IfAny<T, Y, N> = 0 extends 1 & T ? Y : N\n\ntype Database =\n SupabaseClientType extends SupabaseClient<infer U>\n ? IfAny<\n U,\n {\n public: {\n Tables: Record<string, any>\n Views: Record<string, any>\n Functions: Record<string, any>\n }\n },\n U\n >\n : {\n public: {\n Tables: Record<string, any>\n Views: Record<string, any>\n Functions: Record<string, any>\n }\n }\n\ntype DatabaseSchema = Database['public']\ntype SupabaseTableName = keyof DatabaseSchema['Tables']\ntype SupabaseTableData<T extends SupabaseTableName> = DatabaseSchema['Tables'][T]['Row']\n\ntype DefaultClientOptions = PostgrestClientOptions\n\ntype SupabaseSelectBuilder<T extends SupabaseTableName> = ReturnType<\n PostgrestQueryBuilder<\n DefaultClientOptions,\n DatabaseSchema,\n DatabaseSchema['Tables'][T],\n T\n >['select']\n>\n\nexport type SupabaseQueryHandler<T extends SupabaseTableName> = (\n query: SupabaseSelectBuilder<T>\n) => SupabaseSelectBuilder<T>\n\nexport interface UseInfiniteQueryProps<T extends SupabaseTableName> {\n tableName: T\n columns?: string\n pageSize?: number\n trailingQuery?: SupabaseQueryHandler<T>\n}\n\ninterface State<TData> {\n data: TData[]\n count: number\n isSuccess: boolean\n isLoading: boolean\n isFetching: boolean\n error: Error | null\n hasInitialFetch: boolean\n requestCounter: number\n}\n\n// --------------------\n// Composable\n// --------------------\n\nexport function useInfiniteQuery<\n TData extends SupabaseTableData<T>,\n T extends SupabaseTableName = SupabaseTableName,\n>(props: UseInfiniteQueryProps<T>) {\n const state = reactive<State<TData>>({\n data: [],\n count: 0,\n isSuccess: false,\n isLoading: false,\n isFetching: false,\n error: null,\n hasInitialFetch: false,\n requestCounter: 0,\n })\n\n const pageSize = computed(() => props.pageSize ?? 20)\n const columns = computed(() => props.columns ?? '*')\n\n const fetchPage = async (skip: number) => {\n // Capture the current request token to validate this request later\n const requestToken = state.requestCounter\n\n if (state.hasInitialFetch && (state.isFetching || state.data.length >= state.count)) {\n return\n }\n\n // Early return if request has been invalidated\n if (requestToken !== state.requestCounter) {\n return\n }\n\n state.isFetching = true\n\n let query = supabase\n .from(props.tableName)\n .select(columns.value, { count: 'exact' }) as unknown as SupabaseSelectBuilder<T>\n\n if (props.trailingQuery) {\n query = props.trailingQuery(query)\n }\n\n const { data, count, error } = await query.range(skip, skip + pageSize.value - 1)\n\n // Verify that this request is still valid before mutating state\n if (requestToken !== state.requestCounter) {\n state.isFetching = false\n return\n }\n\n if (error) {\n console.error(error)\n state.error = error\n } else {\n state.data.push(...(data as TData[]))\n state.count = count || 0\n state.isSuccess = true\n state.error = null\n }\n\n state.isFetching = false\n }\n\n const fetchNextPage = async () => {\n if (state.isFetching) return\n await fetchPage(state.data.length)\n }\n\n const reset = () => {\n state.data = []\n state.count = 0\n state.isSuccess = false\n state.error = null\n state.hasInitialFetch = false\n state.isFetching = false\n state.requestCounter++\n }\n\n const initialize = async () => {\n state.isLoading = true\n reset()\n\n await fetchNextPage()\n\n state.isLoading = false\n state.hasInitialFetch = true\n }\n\n // React-style deps → Vue watch\n watch(\n () => [props.tableName, props.columns, props.pageSize],\n () => {\n if (state.hasInitialFetch) {\n initialize()\n }\n }\n )\n\n onMounted(() => {\n if (!state.hasInitialFetch) {\n initialize()\n }\n })\n\n const hasMore = computed(() => state.count > state.data.length)\n\n return {\n ...toRefs(state),\n hasMore,\n fetchNextPage,\n refresh: initialize,\n }\n}\n",
"type": "registry:component",
"target": "composables/useInfiniteQuery.ts"
}
]
}