mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 06:09:08 +03:00
## 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>
19 lines
5.1 KiB
JSON
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"
|
|
}
|
|
]
|
|
} |