diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74f8b2d85..277ca710a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
+## [1.4.6-0](https://github.com/nuxt-community/supabase-module/compare/v1.4.5...v1.4.6-0) (2025-01-10)
+
+
+### Features
+
+* Added useSupabaseQuery to simplify fetching ([4448ad3](https://github.com/nuxt-community/supabase-module/commit/4448ad306e2c5857e76c981e631e39f75e566043))
+
## [1.4.5](https://github.com/nuxt-community/supabase-module/compare/v1.4.4...v1.4.5) (2024-12-18)
## [1.4.4](https://github.com/nuxt-community/supabase-module/compare/v1.4.3...v1.4.4) (2024-12-10)
diff --git a/docs/content/4.usage/composables/useSupabaseQuery.md b/docs/content/4.usage/composables/useSupabaseQuery.md
new file mode 100644
index 000000000..753f92e25
--- /dev/null
+++ b/docs/content/4.usage/composables/useSupabaseQuery.md
@@ -0,0 +1,41 @@
+---
+title: useSupabaseQuery
+description: Abstraction around the Supabase select API similar to useAsyncData
+---
+
+This composable is using [postgres-js](https://github.com/supabase/postgrest-js) under the hood.
+
+## Usage
+
+The useSupabaseQuery composable is wrapping the methods and return types of `useSupabaseClient().schema('...').from('...').select('...')`. For a more convinient developer experience similar to using useAsyncData.
+
+```vue
+
+```
+## Options
+### Count
+When using `{count: 'exact' | 'estimated' | 'planned'}` an additional count ref will be returned with a number.
+### Single
+When `single: true` .single() is automatically called, and data is properly typed for a single result instead of an Array
+### Limit
+When specifying a `limit`, an additional loadMore object is returned which will make an additional request using `.range(data.value.length, limit)`, and push it to `data.value`
+### Schema
+A schema can be provied, although `public` is already selected as the default.
+## Typescript
+
+Database typings are handled automatically.
+
+You can also pass Database typings and Schema manually:
+
+```vue
+
+```
diff --git a/package.json b/package.json
index 1cacd8073..58554694e 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@nuxtjs/supabase",
- "version": "1.4.5",
+ "version": "1.4.6-0",
"description": "Supabase module for Nuxt",
"repository": {
"type": "git",
diff --git a/src/runtime/composables/useSupabaseQuery.ts b/src/runtime/composables/useSupabaseQuery.ts
new file mode 100644
index 000000000..52c6bd84a
--- /dev/null
+++ b/src/runtime/composables/useSupabaseQuery.ts
@@ -0,0 +1,121 @@
+import type { PostgrestError, PostgrestFilterBuilder, PostgrestTransformBuilder, UnstableGetResult } from '@supabase/postgrest-js'
+import type { GenericSchema, GenericTable, GenericView } from '@supabase/supabase-js/dist/module/lib/types'
+import type { AsyncDataRequestStatus } from 'nuxt/app'
+import { reactive, toRefs, watchEffect, type Ref } from 'vue'
+import { useNuxtApp, useSupabaseClient } from '#imports'
+import type { Database as ImportedDB } from '#build/types/supabase-database'
+
+type _StrippedPostgrestFilterBuilder, Result, RelationName, Relationships > = Omit, Exclude, 'order' | 'range' | 'limit'>>
+
+type StrippedPostgrestFilterBuilder, Result, RelationName, Relationships > = {
+ [K in keyof _StrippedPostgrestFilterBuilder]: PostgrestFilterBuilder[K]
+}
+
+export function useSupabaseQuery<
+ const Database extends Record & ImportedDB,
+ _single extends boolean = false,
+ _count extends 'exact' | 'planned' | 'estimated' | undefined = undefined,
+ _limit extends number | undefined = undefined,
+
+ const SchemaName extends string & keyof Database = 'public',
+ const Schema extends GenericSchema = Database[SchemaName],
+ const RelationName extends string = string & (keyof Database[SchemaName]['Tables'] | keyof Database[SchemaName]['Views']),
+ const Relation extends GenericTable | GenericView = RelationName extends keyof Schema['Tables'] ? Schema['Tables'][RelationName] : RelationName extends keyof Schema['Views'] ? Schema['Views'][RelationName] : never,
+ const Query extends string = '*',
+ ResultOne = UnstableGetResult,
+ _returning = {
+ data: Ref<_single extends true ? ResultOne : ResultOne[]>
+ error: Ref
+ status: Ref
+ count: _count extends undefined ? undefined : Ref
+ loadMore: _limit extends undefined ? undefined : (() => Promise)
+ },
+>(
+ relation: RelationName,
+ query: Query,
+ filter: (builder: StrippedPostgrestFilterBuilder) => StrippedPostgrestFilterBuilder,
+ { single, count, limit, schema }: {
+ single?: _single
+ count?: _count
+ limit?: _limit
+ schema?: SchemaName
+ } = {},
+): Promise<_returning> & _returning {
+ const nuxtApp = useNuxtApp()
+ const client = useSupabaseClient()
+
+ const asyncData = reactive({ status: 'idle', data: null, error: null })
+ const returning = toRefs(asyncData)
+
+ function makeRequest() {
+ const request = schema
+ ? client.schema(schema).from(relation).select(query, { count })
+ : client.from(relation).select(query, { count })
+ const filteredRequest = filter(request as unknown as StrippedPostgrestFilterBuilder) as unknown as PostgrestTransformBuilder
+ if (single) filteredRequest.single()
+ if (limit) filteredRequest.limit(limit)
+ return filteredRequest
+ }
+
+ const req = makeRequest()
+ // @ts-expect-error Property 'url' is protected and only accessible within class 'PostgrestBuilder' and its subclasses.
+ const key = req.url.pathname + req.url.search
+ if (import.meta.browser) {
+ // Watch for changes
+ let reqInProgress: ReturnType
+ watchEffect(async () => {
+ if (reqInProgress) await reqInProgress
+ reqInProgress = handleRequest(makeRequest())
+ })
+
+ // loadMore
+ if (limit && !single)
+ Object.assign(returning, {
+ async loadMore() {
+ if (!Array.isArray(asyncData.data)) throw new Error('asyncData.data is not an array, so more values cannot be loaded into it.')
+ asyncData.status = 'pending'
+ const { data, error, count } = await makeRequest().range(asyncData.data.length, asyncData.data.length + limit)
+ Object.assign(asyncData, { error, count })
+ asyncData.data.push(data)
+ asyncData.status = error ? 'error' : 'success'
+ },
+ })
+ }
+
+ async function handleRequest(request: ReturnType) {
+ asyncData.status = 'pending'
+ // @ts-expect-error Property 'url' is protected and only accessible within class 'PostgrestBuilder' and its subclasses.
+ const { data, error, count } = nuxtApp.payload.data[req.url.pathname + req.url.search] || await request
+ Object.assign(asyncData, { data, error, count })
+ asyncData.status = error ? 'error' : 'success'
+ return returning
+ }
+
+ const promise = new Promise(resolve => handleRequest(req).then(resolve))
+
+ if (import.meta.server) {
+ promise.finally(() => nuxtApp.payload.data[key] ??= returning)
+ nuxtApp.hook('app:created', async () => {
+ await promise
+ })
+ }
+
+ return Object.assign(promise, returning)
+}
+
+export interface useSupabaseQueryOptions<
+ _single extends boolean = false,
+ _count extends 'exact' | 'planned' | 'estimated' | undefined = undefined,
+ _limit extends number | undefined = undefined,
+ _schema extends string = 'public',
+> {
+ // immediate?: AsyncDataOptions['immediate']
+ // deep?: AsyncDataOptions['deep']
+ // lazy?: AsyncDataOptions['lazy']
+ // server?: AsyncDataOptions['server']
+ // watch?: AsyncDataOptions['watch']
+ single?: _single
+ schema?: _schema
+ limit?: _limit
+ count?: _count
+}