supabase / supabase-js

An isomorphic Javascript client for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse typescript examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
https://supabase.com
MIT License
2.86k stars 220 forks source link

Custom fetch per request #438

Open enyo opened 2 years ago

enyo commented 2 years ago

Feature request

When using the load function in Svelte Kit, it provides a fetch implementation that can be used.

From the official docs:

SvelteKit's load receives an implementation of fetch, which has the following special properties:

  • it has access to cookies on the server
  • it can make requests against the app's own endpoints without issuing an HTTP call
  • it makes a copy of the response when you use it, and then sends it embedded in the initial page load for hydration

Describe the solution you'd like

I think that it would be necessary to specify the fetch on a "per request" basis. Something like this:

supabase.withFetch(fetch).from('mytable').select();

Full example:

<script context="module">
  export const prerender = true;
  import { supabase } from "$lib/initSupabase";

  export async function load({ params, fetch, session, stuff }) {
    let { data, error } = await supabase.from("users").select();

    if (error) {
      return {
        status: 500,
      };
    } else {
      return {
        props: {
          users: data,
        },
      };
    }
  }
</script>

Describe alternatives you've considered

Maybe I'm overthinking this. The way I've solved this so far is by creating a function:

export const getSupabase = (fetch) => createClient(url, anonKey, { fetch });

I then use it in my load function.

Is there a downside in creating multiple clients like this? If not, this issue can probably be closed.

soedirgo commented 2 years ago

Not sure about Auth and Realtime, but for REST this should be fine.

Alternatively (and this is a bit hacky):

// initSupabase.js
let currentFetch;

export function setFetch(fetch) { // call this per request
  currentFetch = fetch;
}

export const supabase = createClient(..., { fetch: (...args) => currentFetch(...args) });
w3b6x9 commented 1 year ago

I don't think this will impact Realtime.

soedirgo commented 1 year ago

There shouldn't be an issue with using multiple clients, so closing this.

5hee75 commented 1 year ago

There shouldn't be an issue with using multiple clients, so closing this.

@soedirgo I have definitely encountered an issue with multiple clients, specifically when using auth listeners. I created a client factory, similar to OP, where each request to the SDK would create a new client. I did this so I could inject dynamic headers and settings into the client for each execution.

const supa = (...params) => createClient(...params);

supa().from...
supa().auth...
// etc

The issue I found was that auth listeners/events are registered to a specific client instance, not to the auth events themselves. I'm not sure if this is intended or not.

So in my example above, if I do this:

const supa = () => createClient(...);

supa().auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_OUT') {
    console.log('Auth Event', event);
  }
});

supa().auth.signOut();

The listener is never called, presumably because I signed out from a different client instance. The auth token and user details are still removed from localStorage though.

enyo commented 1 year ago

Oh man @5hee75 I was wondering forever why this happened in my app. This explains it perfectly.

johndeighan commented 1 year ago

I'm doing a lot of SvelteKit these days and I'd definitely like to see the ability to specify the fetch to use per request. Although everything works in my project, I'm not happy seeing the message from svelte about using window.fetch rather then their version. And I'm not convinced that creating a new database connection for each query is a good thing.

enyo commented 1 year ago

I haven’t yet found out why exactly, but I upgraded svelte kit + supabase, and now using a new supabase instance for an individual route breaks the routing completely. The page just never stops loading and there is no error. It’s very strange but it has definitely something to do with using a different supabase instance because if I just use the global instance it works fine.

EDIT: I realised that even just calling createClient() will break page rendering, even if then the singleton supabase instance is used. I assume that this has something to do with the auth state being broken.

enyo commented 1 year ago

Not sure about Auth and Realtime, but for REST this should be fine.

Alternatively (and this is a bit hacky):

// initSupabase.js
let currentFetch;

export function setFetch(fetch) { // call this per request
  currentFetch = fetch;
}

export const supabase = createClient(..., { fetch: (...args) => currentFetch(...args) });

@soedirgo This would be an acceptable solution but it runs into race conditions. Especially on the server during SSR this might mix up two requests.

MLNW commented 12 months ago

This would be a great addition. I like to use the handleFetch hook from SvelteKit to log all fetches but for that to work Supabase would have to use the SvelteKit provided fetch function.

ConProgramming commented 2 months ago

This can also be done when creating the client in hooks.server.ts, e.g.

    event.locals.sb = createSupabaseServerClient<Database>({
        supabaseUrl: PUBLIC_SUPABASE_URL,
        supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
        event,
        options: {
            global: {
                fetch: event.fetch,
            }
        }
    });