Loomup Docs
Guide /docs/sdk-vue

Vue SDK

Package: @loomup/vue (SDK repository path: packages/vue).

Thin Vue 3 bindings on top of @loomup/client: plugin, auth session, REST query composables, and realtime live queries.

Install

bash
# from a project that can reach the monorepo paths
npm install ../path/to/packages/client ../path/to/packages/vue
# or after publish:
# npm install @loomup/client @loomup/vue

Peer dependencies: vue ≥ 3.3, @loomup/client ≥ 0.1.0.

Quick start

ts
// main.ts
import { createApp } from "vue";
import { createClient } from "@loomup/client";
import { LoomupPlugin } from "@loomup/vue";
import App from "./App.vue";

// Keep the client stable (module scope).
const client = createClient({ url: "http://127.0.0.1:3000" });

const app = createApp(App);
app.use(LoomupPlugin, {
  client,
  persist: { enabled: true },
});
app.mount("#app");
vue
<script setup lang="ts">
import { useAuth, useLiveQuery, useMutation } from "@loomup/vue";

const { user, loading: authLoading, signIn, signUp, signOut } = useAuth();
const { data, loading, ready } = useLiveQuery("todos", {
  sort: "-id",
  limit: 50,
  // "refetch" (default) is safest with where/rules;
  // "merge" applies INSERT/UPDATE/DELETE/RESYNC to local state.
  strategy: "merge",
  enabled: () => !!user.value,
});
const { mutate: addTodo } = useMutation((c, title: string) =>
  c.from("todos").insert({ title, completed: 0 }),
);
</script>

<template>
  <p v-if="authLoading">Loading…</p>
  <button
    v-else-if="!user"
    type="button"
    @click="signIn({ email: 'a@b.com', password: 'secret12' })"
  >
    Sign in
  </button>
  <div v-else>
    <p>
      {{ user.email }}
      <button type="button" @click="signOut()">Sign out</button>
    </p>
    <button type="button" @click="addTodo('Ship Vue SDK')">Add</button>
    <p v-if="loading">Loading todos…</p>
    <ul>
      <li v-for="row in data ?? []" :key="String(row.id)">
        {{ row.title }}
      </li>
    </ul>
    <small v-if="ready">Live</small>
  </div>
</template>

You can also pass options={{ url }} instead of client and the plugin will create a client once at install.

For tests or nested trees without app.use, call provideLoomup({ client }) from a parent setup().

API

LoomupPlugin

ts
app.use(LoomupPlugin, {
  client, // existing LoomupClient (preferred)
  // options, // CreateClientOptions when client is omitted
  persist: {
    enabled: true, // store tokens in localStorage
    storageKey: "loomup", // key prefix
  },
});
OptionDescription
clientExisting LoomupClient (preferred).
optionsCreateClientOptions when client is omitted.
persist.enabledStore access/refresh tokens in localStorage.
persist.storageKeyKey prefix (default loomup).

provideLoomup(options)

Same options as the plugin; uses Vue provide for the current component tree.

useLoomup()

Returns the client from inject. Throws outside the plugin / provideLoomup.

useAuth()

Reactive fields are Vue Refs (auto-unwrapped in templates).

FieldDescription
userCurrent user or null.
session{ accessToken, refreshToken }.
loading / errorAsync state.
signIn / signUp / signOut / refresh / meSession methods.

On mount, if the client already has an access token (or one is restored from storage), calls auth.me().

useSelect(table, opts?) / useRow(table, id, opts?)

REST list / get with loading, error, refetch. Options: where, sort, limit, offset, enabled.

enabled and useRow's id accept plain values, refs, or getters (MaybeRefOrGetter).

useMutation(fn)

ts
const { mutate, loading, error, reset } = useMutation((client, ...args) =>
  client.from("todos").insert(...),
);

useSubscribe(table, handler, opts?)

Subscribes on setup; unsubscribes on unmount. Uses subscribeReady by default (waitForAck: true). Handler identity can change without resubscribing.

useLiveQuery(table, opts?)

Initial select + realtime subscription.

strategyBehavior
refetch (default)Re-run select on every change (correct with filters/rules).
mergeApply INSERT/UPDATE/DELETE/RESYNC into the local array.

Filtered lists cannot perfectly re-evaluate server where/rules client-side; prefer refetch when filters matter.

Types

Use generated maps from the core SDK:

bash
loomup gen typescript --config loomup.toml --output ./loomup-types.ts
ts
import { createClient } from "@loomup/client";
import type { TableMap, TableInsertMap, TableUpdateMap } from "./loomup-types";

const client = createClient<TableMap, TableInsertMap, TableUpdateMap>({
  url: "http://127.0.0.1:3000",
});

@loomup/vue re-exports createClient, LoomupError, and common types for convenience.

Example

bash
cd examples/todo-vue && npm install && npm run dev
# against a running loomup serve on :3000

Publish workflow (maintainers)

  1. Publish or link @loomup/client first.
  2. Bump packages/vue/package.json version.
  3. npm test / npm run build in packages/vue.
  4. npm publish --access public.

Non-goals (v1)

  • Nuxt module / cookie SSR helpers (browser / SPA first).
  • Vue 2 / Options API–only wrappers.
  • Offline sync.