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..e6d75c48b
--- /dev/null
+++ b/src/runtime/composables/useSupabaseQuery.ts
@@ -0,0 +1,181 @@
+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, Database as ImportedDB } from '#build/types/supabase-database'
+
+type _StrippedPostgrestFilterBuilder, Result, RelationName = unknown, Relationships = unknown> = Omit, Exclude, 'order' | 'range' | 'limit'>>
+
+type StrippedPostgrestFilterBuilder, Result, RelationName = unknown, Relationships = unknown > = {
+ [K in keyof _StrippedPostgrestFilterBuilder]: PostgrestFilterBuilder[K]
+}
+
+type FilterFn = Record, Result = unknown, RelationName = unknown, Relationships = unknown> = (filter: StrippedPostgrestFilterBuilder) => StrippedPostgrestFilterBuilder
+// export declare interface useSupabaseSelect<_db extends Record = ImportedDB> extends useSupabaseSelectSchema<_db['public']> {
+// schema<_schema extends keyof _db>(schema: _schema): useSupabaseSelectSchema<_db[_schema]>
+// }
+
+// interface useSupabaseSelectSchema<
+// _schema extends GenericSchema,
+// > {
+// <
+// _relation extends keyof (_schema['Views'] & _schema['Tables']) = keyof (_schema['Views'] & _schema['Tables']),
+// _relationTableOrView extends GenericTable | GenericView = _relation extends keyof _schema['Tables'] ? _schema['Tables'][_relation] : _relation extends keyof _schema['Views'] ? _schema['Views'][_relation] : never,
+// _query extends string = '*',
+// _result = UnstableGetResult<_schema, _relationTableOrView['Row'], _relation, _relationTableOrView['Relationships'], _query>,
+// _builder = StrippedPostgrestFilterBuilder<_schema, _relationTableOrView['Row'], _result, _relationTableOrView, _relationTableOrView['Relationships'] >,
+// _count extends 'exact' | 'planned' | 'estimated' | undefined = undefined,
+// _single extends boolean = false,
+// _returning = useSupabaseSelectReturns<_single extends true ? _result : _result[]> & _count extends undefined ? never : { count: Ref } & _single extends true ? never : { loadMore(count: number): Promise },
+// >(relation: _relation, query: _query, options?: {
+// filter?: (builder: _builder) => _builder
+// count?: _count
+// single?: _single
+// }): _returning & Promise<_returning>
+// }
+
+interface useSupabaseSelectReturns<_result> {
+ data: Ref<_result>
+ error: Ref
+ status: Ref
+}
+
+export function useSupabaseSelect<
+ _db extends Record = ImportedDB,
+ _schema extends GenericSchema = _db['public'],
+ const _relation extends string = (string & keyof _schema['Views']) | (string & keyof _schema['Tables']),
+ _relationTableOrView extends GenericTable | GenericView = _relation extends keyof _schema['Tables'] ? _schema['Tables'][_relation] : _relation extends keyof _schema['Views'] ? _schema['Views'][_relation] : never,
+ _result = UnstableGetResult<_schema, _relationTableOrView['Row'], _relation, _relationTableOrView['Relationships'], '*'>,
+ _returning = useSupabaseSelectReturns<_result[]> & { loadMore(count: number): Promise },
+>(relation: _relation): _returning & Promise<_returning>
+
+export function useSupabaseSelect<
+ _db extends Record = ImportedDB,
+ const _schema extends GenericSchema = _db['public'],
+ const _relation extends string = (string & keyof _schema['Views']) | (string & keyof _schema['Tables']),
+ _relationTableOrView extends GenericTable | GenericView = _relation extends keyof _schema['Tables'] ? _schema['Tables'][_relation] : _relation extends keyof _schema['Views'] ? _schema['Views'][_relation] : never,
+ _query extends string = string,
+ _result = UnstableGetResult<_schema, _relationTableOrView['Row'], _relation, _relationTableOrView['Relationships'], _query>,
+ _returning = useSupabaseSelectReturns<_result[]> & { loadMore(count: number): Promise },
+>(relation: _relation, query: _query): _returning & Promise<_returning>
+
+export function useSupabaseSelect<
+ _db extends Record = ImportedDB,
+ const _schema extends GenericSchema = _db['public'],
+ const _relation extends string = (string & keyof _schema['Views']) | (string & keyof _schema['Tables']),
+ _relationTableOrView extends GenericTable | GenericView = _relation extends keyof _schema['Tables'] ? _schema['Tables'][_relation] : _relation extends keyof _schema['Views'] ? _schema['Views'][_relation] : never,
+ const _query extends string = string,
+ _result = UnstableGetResult<_schema, _relationTableOrView['Row'], _relation, _relationTableOrView['Relationships'], _query>,
+ _count extends 'exact' | 'planned' | 'estimated' | undefined = 'exact' | 'planned' | 'estimated' | undefined,
+ _single extends boolean = boolean,
+ _returning = (useSupabaseSelectReturns<_single extends true ? _result : _result[]>) & { count: _count extends undefined ? never : Ref, loadMore: _single extends true ? never : (count: number) => Promise } ,
+>(relation: _relation, query: _query, options?: {
+ filter?: FilterFn<_schema, _relationTableOrView['Row'], _result, _relationTableOrView, _relationTableOrView['Relationships'] >
+ count?: _count
+ single?: _single
+}): _returning & Promise<_returning>
+
+export function useSupabaseSelect(relation: string, query: string = '*', { count, single, filter }: { filter?: FilterFn, count?: 'exact' | 'planned' | 'estimated', single?: boolean } = {}) {
+ return _interal({ count, single, relation, query, schema: 'public', filter })
+}
+
+export function useSupabaseSelectSchema<
+ _db extends Record = ImportedDB,
+ const _schema_name extends string = string & keyof _db,
+ const _schema extends GenericSchema = _db[_schema_name],
+>(schema: _schema_name): typeof useSupabaseSelect<_db, _schema> {
+ return function useSupabaseSelect<_db, _schema>(relation: string, query: string = '*', { count, single, filter }: { filter?: FilterFn, count?: 'exact' | 'planned' | 'estimated', single?: boolean } = {}) {
+ return _interal({ count, single, relation, query, schema, filter })
+ }
+}
+
+function _interal<
+ _returning = {
+ data: Ref
+ error: Ref
+ status: Ref
+ count?: Ref
+ loadMore?: (() => Promise)
+ },
+>({
+ schema,
+ relation,
+ filter,
+ query,
+ single,
+ count,
+}: {
+ schema: string
+ relation: string
+ query: string
+ filter?: FilterFn
+ single?: boolean
+ count?: 'exact' | 'planned' | 'estimated'
+}): Promise<_returning> & _returning {
+ const nuxtApp = useNuxtApp()
+ const client = useSupabaseClient()
+
+ const asyncData = reactive({ status: 'idle', data: null, error: null })
+ const returning = toRefs(asyncData)
+
+ function makeRequest() {
+ let request = schema
+ ? client.schema(schema).from(relation).select(query, { count })
+ : client.from(relation).select(query, { count })
+ if (filter) request = filter(request) // as unknown as StrippedPostgrestFilterBuilder) as unknown as PostgrestTransformBuilder
+ if (single) request.single()
+ return request
+ }
+
+ 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)
+}
+
+// immediate?: AsyncDataOptions['immediate']
+// deep?: AsyncDataOptions['deep']
+// lazy?: AsyncDataOptions['lazy']
+// server?: AsyncDataOptions['server']
+// watch?: AsyncDataOptions['watch']