Skip to content

Conversation

@Gerbuuun
Copy link
Collaborator

@Gerbuuun Gerbuuun commented Oct 15, 2025

createZero is not a composable because it works outside of the vue context. The function passed in does run with the Vue context.

The idea is that you can call useZero anywhere and the same client will be provided down the component tree whenever you call useZero again. It's only initialised once

S extends Schema,
MD extends CustomMutatorDefs | undefined = undefined,
>(options: () => ZeroOptions<S, MD>) {
let zero: Zero<S, MD>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind not making zero a shallowRef here? Wouldn't that mean that reactivity of your existing queries breaks when a new zero instance is created?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Nuxt you cannot define reactive state outside of script setup because the value will be shared by all users during SSR. But I'm not so sure now how this would work here because zero is still a global value... This is an SSR problem for sure

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point I really need to opinion of @danielroe

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think the changes I made in 57a7b81 should prevent leaks of the zero instance between Nuxt requests while maintaining reactivity, as long as useZero is called inside a setup context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say we just do it like that for now. One thing to change is how zero is initialised. The way I did it before (and you later implemented in your PR) doesn't allow for async things like fetching a token. What I have now works

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the options are passed to useZero, it'll muddle the responsibility of the function IMO, as you technically only have to call useZero with options once (to create the instance), after which it can be used without options (to just provide the already created instance). Keeping that responsibility separated between createZero (for creating) and useZero (for providing the instance) is clearer for consumers (they don't need to know/care that we actually create the instance lazily in useZero).

To deal with async fetching, you can already pass an async getter to the auth option, and to fetch for example the userID async you can use a reactive variable or store and do the fetching somewhere in the background. To give you an idea, this is what our internal usage would look like with these changes. When the user in the userStore changes, a new zero instance is created:

let lastClaims: AuthData;

export const { useZero, useQuery } = createZero(() => { 
    const userStore = useUserStore();

    return {
        schema,
        userID: userStore.firebaseUser?.uid ?? "anon",
        server: import.meta.env.VITE_ZERO_CACHE_URL,
        mutators: createMutators(() => lastClaims),
        auth: async () => {
            if (!userStore.firebaseUser) {
                return;
            }

            const { token, claims } = await userStore.firebaseUser.getIdTokenResult();
            if (!isEqual(lastClaims, claims) && claims.sub) {
                lastClaims = claims as AuthData;
            }

            return token;
        },
    }
});

My proposal is that we move on with #119 (pending approval from @danielroe), which should be good to merge by now.

Copy link
Collaborator Author

@Gerbuuun Gerbuuun Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But here the options aren't reactive. I really don't want to hold up this release either but it also gotta work 😅

A small example:

export function useBasketSession(shopId: MaybeRefOrGetter<ID<'order'>>) {
  const headers = useRequestHeaders(['cookie']);
  const session = ref(null);

  async function fetch() {
    const res = await $fetch(`/api/shop/${toValue(shopId)}/basket`, {
      method: 'GET',
      headers,
    });
    session.value = res;
  }

  async function clear() {
    await $fetch(`/api/shop/${toValue(shopId)}/basket`, {
      method: 'DELETE',
      headers,
    });
    session.value = null;
  }

  return {
    session,
    fetch,
    clear,
  }
}

export const { useZero: useBasket, useQuery: useBasketQuery } = createZero<Schema, ClientMutators>(() => {
  const { session } = useBasketSession();
  const config = useRuntimeConfig().public.zero;

  return {
    userID: session.value?.orderId ?? 'anon',
    auth: session.value?.token,
    schema: zeroSchema,
    mutators: clientMutators(session.value?.decoded),
    server: import.meta.client ? config.server : undefined,
    kvStore: 'mem', // No need to persist the basket
  }
});

This will start the zero client in the page:

<script setup lang="ts">
const route = useRoute();
const { fetch } = useBasketSession(route.shopId);
await fetch();

const basket = useBasket();
const { data: items } = useBasketQuery(basket.query...);
// etc..
</script>

But clearing the value and refetching a new session will not recreate the zero client

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gerbuuun see my comment here

@Gerbuuun
Copy link
Collaborator Author

implemented in #119

@Gerbuuun Gerbuuun closed this Oct 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants