Supabase
@ginjou/with-supabase provides a Ginjou fetcher and a Ginjou auth provider for Supabase.
Most apps pass the same Supabase client to both.
It does not change the higher-level Ginjou API. It only connects those hooks to the Supabase JavaScript client.
Installation
Install @supabase/supabase-js together with the adapter.
pnpm add @ginjou/with-supabase @supabase/supabase-js
yarn add @ginjou/with-supabase @supabase/supabase-js
npm install @ginjou/with-supabase @supabase/supabase-js
bun add @ginjou/with-supabase @supabase/supabase-js
| Package | Supported version |
|---|---|
@supabase/supabase-js | ^2.7.0 |
Fetcher
Use createFetcher() with a Supabase client.
| Prop | Required | Meaning |
|---|---|---|
client | Yes | A Supabase client instance. |
Register it through defineFetchersContext().
import { defineFetchersContext } from '@ginjou/vue'
import { createFetcher } from '@ginjou/with-supabase'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key',
)
defineFetchersContext({
default: createFetcher({ client: supabase }),
})
| Support level | Methods |
|---|---|
| Implemented | getList, getMany, getOne, createOne, createMany, updateOne, deleteOne |
| Not implemented | custom, updateMany, deleteMany |
Pagination
List pagination uses Supabase range().
| Ginjou input | Supabase behavior |
|---|---|
pagination.current | Used to calculate the starting row |
pagination.perPage | Used to calculate the ending row |
The adapter calls:
| Range argument | Value |
|---|---|
from | (current - 1) * perPage |
to | current * perPage - 1 |
For totals, getList() uses Supabase count mode from meta.count and returns count || 0.
Filters
The adapter maps Ginjou filters to Supabase PostgREST filter helpers.
| Ginjou operator | Supabase behavior |
|---|---|
eq | eq(field, value) |
ne | neq(field, value) |
in | in(field, value) |
gt | gt(field, value) |
gte | gte(field, value) |
lt | lt(field, value) |
lte | lte(field, value) |
contains | ilike(field, %value%) |
containss | like(field, %value%) |
null | is(field, null) |
startswith | ilike(field, value%) |
endswith | ilike(field, %value) |
or | or(...) with joined logical clauses |
These operators use the generic filter() fallback.
| Ginjou operator | Generic fallback operator |
|---|---|
nin | not.in |
ncontains | not.ilike |
ncontainss | not.like |
nnull | not.is |
These operators are not supported and throw instead of being translated.
| Operator | Behavior |
|---|---|
and | Throws an error |
between | Throws an error |
nbetween | Throws an error |
Sorters
Sorters are applied through Supabase order().
Normal fields use order(field, { ascending }).
Related table fields can use dot notation.
| Ginjou sorter | Supabase behavior |
|---|---|
{ field: 'title', order: 'asc' } | order('title', { ascending: true }) |
{ field: 'profile.name', order: 'desc' } | order('name', { ascending: false, foreignTable: 'profile' }) |
When dot notation is used, the adapter also expands the select string so Supabase can order through the related table.
Meta
These meta fields are verified in the adapter.
| Meta field | Used by | Default | Purpose |
|---|---|---|---|
select | All fetcher methods | '*' where applicable | PostgREST select expression |
count | getList | 'exact' | Count strategy for pagination total |
idColumnName | getMany, getOne, updateOne, deleteOne | 'id' | Custom primary key column |
Use meta.idColumnName when the record key is not stored in an id column.
import { useGetOne } from '@ginjou/vue'
useGetOne({
resource: 'orders',
id: 'order-001',
meta: {
idColumnName: 'order_id',
},
})
Use meta.select to control the returned columns and relations.
import { useGetList } from '@ginjou/vue'
useGetList({
resource: 'posts',
meta: {
select: 'id,title,author:users(id,name)',
},
})
Use meta.count to choose the Supabase count mode for getList().
import { useGetList } from '@ginjou/vue'
useGetList({
resource: 'posts',
meta: {
count: 'planned',
},
})
Auth
Use createAuth() with the same Supabase client.
Most apps reuse the client they already passed to createFetcher().
Register it through defineAuthContext().
import { defineAuthContext } from '@ginjou/vue'
import { createAuth } from '@ginjou/with-supabase'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key',
)
defineAuthContext(createAuth({ client: supabase }))
Login
login() dispatches to one of the Supabase auth client methods based on type.
| Login type | Supabase client call |
|---|---|
password | signInWithPassword() |
oauth | signInWithOAuth() |
idtoken | signInWithIdToken() |
otp | signInWithOtp() |
sso | signInWithSSO() |
otp-token | verifyOtp() |
Password login:
import { useLogin } from '@ginjou/vue'
const { mutateAsync: login } = useLogin()
await login({
type: 'password',
params: {
email: 'user@example.com',
password: 'password123',
},
})
OAuth login:
import { useLogin } from '@ginjou/vue'
const { mutateAsync: login } = useLogin()
await login({
type: 'oauth',
params: {
provider: 'github',
},
})
Logout
logout() maps to client.auth.signOut().
It forwards the optional logout params to Supabase.
Identity
getIdentity() maps to client.auth.getUser().
It returns the Supabase user object.
Check Authentication
check() maps to client.auth.getSession().
| Session state | Result |
|---|---|
| Valid session | { authenticated: true } |
| No valid session and no auth error | { authenticated: false, logout: false, error: null } |
| Auth error | { authenticated: false, logout: true, error } |
Auth failures are returned as part of the result instead of being thrown.
Check Error
checkError() uses Supabase isAuthError().
| Error kind | Result |
|---|---|
| Auth error | { logout: true, error } |
| Other error | {} |