npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@convex-vue/core

v0.0.4

Published

Vue integration for Convex

Downloads

121

Readme

@convex-vue/core

Get Started

  • Install the package and its peer dependencies
npm install @convex-vue/core @vueuse/core convex vue-router

Simple example

const convexVue = createConvexVue({
  convexUrl: import.meta.env.VITE_CONVEX_URL
});

app.use(convexVue);

Example with auth using auth0

// Example with auth using auth0
const auth = createAuth0({
  domain: import.meta.env.VITE_AUTH0_DOMAIN,
  clientId: import.meta.env.VITE_AUTH0_CLIENTID,
  authorizationParams: {
    redirect_uri: window.location.origin
  }
});

const convexVue = createConvexVue({
  convexUrl: import.meta.env.VITE_CONVEX_URL,
  auth: {
    isAuthenticated: auth.isAuthenticated,
    isLoading: auth.isLoading,
    getToken: async ({ forceRefreshToken }) => {
      try {
        const response = await auth.getAccessTokenSilently({
          detailedResponse: true,
          cacheMode: forceRefreshToken ? 'off' : 'on'
        });
        return response.id_token;
      } catch (error) {
        return null;
      }
    },
    installNavigationGuard: true,
    needsAuth: to => to.meta.needsAuth
    redirectTo: () => ({
      name: 'Login'
    })
  }
});

app.use(convexVue);
  • You can now use the convex-vue composables and components in your app 😊

Composables

useConvexQuery

Subscribes to a convex query. It expose a suspense function to enable use inside a <Suspense /> boundary.

<script setup lang="ts">
  import { api } from '../convex/_generated/api';

  const { data, isLoading, error, suspense } = useConvexQuery(
    api.todos.list, // the query name
    { completed: true } // query arguments, if no arguments you need to pass an empty object. It can be ref
  );

  await suspense(); // if used, must be called as a child of <Suspense/> component
</script>

useConvexPaginatedQuery

Subscribes to a convex query and handles pagination. It expose a suspense function to enable use inside a <Suspense /> boundary that will load the first page.

<script setup lang="ts">
  import { api } from '../convex/_generated/api';

  const {
    data,
    lastPage,
    isLoading,
    isLoadingMore,
    isDone,
    loadMore,
    reset,
    pages,
    error,
    suspense,
  } = useConvexPaginatedQuery(
    api.todos.list, // the query name
    { completed: true } // query arguments, if no arguments you need to pass an empty object. It can be ref,
    { numItems: 50 } // the number of items per page
  );

  await suspense(); // if used, must be called as a child of <Suspense/> component
</script>

useConvexMutation

Handles convex mutations. Optimistic updates are supported.

const { isLoading, error, mutate: addTodo } = useConvexMutation(api.todos.add, {
  onSuccess() {
    todo.value = '';
  },
  onError(err) {
   console.error(err)
  },
  optimisticUpdate(ctx) {
    const current = ctx.getQuery(api.todos.list, {});
    if (!current) return;

    ctx.setQuery(api.todos.list, {}, [
      {
        _creationTime: Date.now(),
        _id: 'optimistic_id' as Id<'todos'>,
        completed: false,
        text: todo.text
      },
      ...current
    ]);
  }
});

useConvexAction

Handles convex actions.

const { isLoading, error, mutate } = useConvexAction(api.some.action, {
  onSuccess(result) {
    console.log(result);
  },
  onError(err) {
    console.error(err);
  }
});

Components

Convex-vue exposes some helpers components to use queries. This can be useful if you solely need it's data in your component templates

<ConvexQuery :query="api.todos.list" :args="{}">
  <template #loading>Loading todos...</template>

  <template #error="{ error }">{{ error }}</template>

  <template #empty>No todos yet.</template>

  <template #default="{ data: todos }">
    <ul>
      <li v-for="todo in todos" :key="todo._id">
        <Todo :todo="todo" />
      </li>
    </ul>
  </template>
</ConvexQuery>

 <ConvexPaginatedQuery
  :query="api.todos.paginatedList"
  :args="{}"
  :options="{ numItems: 5 }"
>
  <template #loading>Loading todos...</template>

  <template #error="{ error, reset }">
    <p>{{ error }}</p>
   <button @click="reset">Retry</button>
  </template>

  <template #default="{ data: todos, isDone, loadMore, isLoadingMore, reset }">
    <ul>
      <li v-for="todo in todos" :key="todo._id">
        <Todo :todo="todo" />
      </li>
    </ul>
    <Spinner v-if="isLoadingMore" />
    <footer>
      <button :disabled="isDone" @click="loadMore">Load more</button>
      <button @click="reset">Reset</button>
    </footer>
  </template>
</ConvexPaginatedQuery>

🧪 Route Loaders (experimental)

Taking inspiration from Remix's route loaders , Convex-vue introduces a mechanism to specify which data a route needs. The data will then start fetching when navigating, before loading the javascript for the page and mouting its component. Under the hood, this fires a Convex client subscription so that, hopefully, by the time the page mounts, the convex client cache will already have the data, or , at least, the request wil lalready be in flight.

You need to use vue-router to use this feature.

  • First, define a route loader map liek below.
import { api } from '@api';
import { defineRouteLoader } from '@convex-vue/core';

// defineRouteLoader will provide you with type safety
export const loaders = {
  Home: defineRouteLoader({
    todos: {
      query: api.todos.list,
      args() {
        return {};
      }
    }
  }),

  TodoDetails: defineRouteLoader({
    todo: {
      query: api.todos.byId,
      args(route) {
        return {
          id: route.params.id as Id<'Todos'>
        };
      }
    }
  })
};

export type Loaders = typeof loaders;
  • Then, pass the rotue loader map to the convex-vue plugin
import { loaders } from './loaders';

const convexVue = createConvexVue({
  convexUrl: import.meta.env.VITE_CONVEX_URL,
  routeLoaderMap: loaders
});

app.use(convexVue);
  • That's it ! Now, when navigating to a page, convex-vue will look in your loader map and search for a loader corresponding to the name property of the route. Alternatively, you can provide a loader property in your routes meta, andit will be used instead.

  • You can also use the useRouteLoader to get akk the data for one loader in one go, instead of using multiple instances of useConvexQuery or useConvexPaginatedQuery.

const { todos } = useRouteLoader<Loaders['Home']>();

⚠️ Due to the way vue-router's route matching works, you will get the data for ALL the matched routes when using nested routes. Be wary of naming conflicts !

  • A <ConvexLink /> component is also available. It just wraps vue-router's <RouterLink /> and will prefetch its target loader on hover. The component accepts a prefetchTimeout prop to set how long the link should be hovered in order to start prefetching
<ConvexLink :to="{ name: 'Home' }">Todos</ConvexLink>