{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "infinite-query-composable",
  "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"
    }
  ],
  "type": "registry:component"
}