Closed Daynil closed 1 year ago
Is this still happening? I couldn't reproduce this in 2.0.0-rc.1 or the latest rc
version.
Thanks for looking into this @soedirgo!
I believe I have pinpointed the issue. I am using a custom fetch implementation, and my fetch call was not receiving the vnd.pgrst.object
header. Likewise, I'm not receiving other headers, like headers['Content-Type'] = 'application/json';
for post requests or headers['prefer'] = 'return=representation'
for non-GET requests.
Would you be able to provide any guidance on why I'm not receiving any headers from supabase here? I used the guidance here.
const supabase = createClient<Database>(
appConfig.frontend.supabase.url,
appConfig.frontend.supabase.anonKey,
{
global: {
fetch: supabaseFetch.bind(globalThis)
}
}
);
export async function supabaseFetch(
input: RequestInfo | URL,
init?: RequestInit
) {
// **** Input contains the fully constructed URL from supabase
// i.e. project.supabase.co/rest/v1/my_table?id=eq.1
console.log('from supabase input: ', input);
// **** init contains method 'GET', but the headers object is empty
// should this contain {Accept: 'application/vnd.pgrst.object+json'} after a .single() call?
// If not, how else can I determine .single was called in my custom fetch?
// i.e. {method: 'GET', headers: {}, body: undefined, signal: undefined}
console.log('from supabase init: ', init);
const authHeader: { Authorization: string; apikey: string } = {
Authorization: `Bearer ${await getSupabaseToken()}`,
apikey: appConfig.frontend.supabase.anonKey
};
const headers: HeadersInit = init.headers
? { ...init.headers, ...authHeader }
: authHeader;
if (init.body) {
// https://postgrest.org/en/stable/api.html#calling-functions-with-a-single-unnamed-parameter
headers['Content-Type'] = 'application/json';
// body already comes stringified from supabase-js
// init.body = JSON.stringify(init.body);
}
if (init.method !== 'GET' && init.method !== 'OPTIONS') {
// Required to get data back from non-GET requests
// https://postgrest.org/en/stable/api.html#insertions-updates
headers['prefer'] = 'return=representation';
}
// **** How do I know when to set this?
if (init.method === 'GET') {
headers['Accept'] = 'application/vnd.pgrst.object+json';
}
// Cast as RequestInfo solves a type error only found in the compiler for some reason
return fetch(input as RequestInfo, {
...init,
headers
});
}
Hmm, can you elaborate what you're aiming for with the custom fetch?
init contains method 'GET', but the headers object is empty
That's odd - it should have headers populated, incl. auth headers and Accept. Does it work if you don't pass a custom fetch?
Yeah, I'm using it to sync with an external auth provider. I need to sync the custom supabase auth key every 30 minutes since it regenerates. Previously, I was using supabase.auth.setAuth(token)
similar to the process described here. Since supabase v2 has removed setAuth, I've been considering the alternatives and this has been my favorite, other than the issue above with init.headers not getting passed through.
I looked into the suggestion in the removal PR to pass the custom header into createClient()
, but since the authentication token refreshes I can't just set it once. Also the call to get the first token is async, so that makes it tricky to export. I guess the other option is to recreate the client for every request, but that doesn't seem as clean/efficient as injecting your headers via the custom fetch?
That's odd - it should have headers populated, incl. auth headers and Accept. Does it work if you don't pass a custom fetch?
It does work fine without the custom fetch. I tried to create a minimal reproduction here (single table todos
with two items, id's 1 and 2) and discovered that it does in fact pass an empty header into the custom fetch call. However, if I just pass both properties back to fetch
without any other modification, in the network tab, all headers are set properly. If I try to set a custom header that supabase has already set (such as the one I need, Authorization
), supabase seems to automatically overwrite it to the default (which is the anon
key). The only way for me to get my custom Authorization
header set is to completely overwrite the headers
object with my own, in which case I need to manually reconstruct the appropriate headers since we aren't getting passed them in the custom fetch call, and for the .single()
call, we can't know whether it was called with single in the custom fetch.
Thanks for the thorough explanation! I'll have a look at the repro link.
Sorry for the late reply - had a look at the repro link (thanks again!) and it seems like this was caused by init.headers
being a Headers object instead of a { [key: string]: string }
. You can get the actual values if you do:
for (const [headerKey, headerValue] of init?.headers?.entries() ?? []) {
console.log('header:', headerKey, headerValue)
}
That said, we don't make any guarantee that this will always be Headers
, so you'll need to handle both cases.
Wow, can't believe I haven't run into this particular javascript idiosyncrasy until now! Thanks @soedirgo!!
I adjusted my code and everything works exactly as expected:
export async function supabaseFetch(
input: RequestInfo | URL,
init?: RequestInit
) {
if (init.headers instanceof Headers) {
init.headers.set('Authorization', `Bearer ${await getSupabaseToken()}`);
} else {
init.headers['Authorization'] = `Bearer ${await getSupabaseToken()}`;
}
return fetch(input, init);
}
Hi I ran into this issue as well recently in a NextJS project in a server component but in a peculiar way.
I was doing
const { data, error } = await supabase
.from('mytable')
.select('*')
.eq('slug', slug)
.single();
This returns an array with one object.
However if I add a second condition: (.eq('column', true')
) to the first, then it just returns an object and not an array.
This is in NextJS in a server component but I'm not doing anything fancy other than this read.
Is this expected behavior of some kind/when does .single() still return an array?
It's not expected behavior - do you have a public repo that demonstrates the issue so I can reproduce it?
Just encountered this issue too. I'm using NextJS.
Bug report
Describe the bug
After updating to supabase-js v2 rc-1, appending
.single()
to a query continues to return an array in the actualdata
. The types from the new type gen properly infer that a single non-array item should be returned when.single()
is used, so manually specifyingdata[0]
produces anany
type inference. However, loggingdata
shows that it was returned as an array. The same code works as expected in the latest supabase-js v1.To Reproduce
Expected behavior
.single()
should cause data to return the single object (or throw if multiple) not an array of the object (as inferred by the types).System information