Skip to content

JavaScript and TypeScript SDK

@vaifstudio/client is the VAIF Studio SDK for JavaScript and TypeScript. It is built on fetch, so the same package runs in browsers, Node.js 22 or later, Bun, Deno, edge runtimes and React Native.

Terminal window
npm install @vaifstudio/client

For React, add @vaifstudio/react. For React Native and Expo, add @vaifstudio/expo.

import { createVaifClient } from "@vaifstudio/client";
const vaif = createVaifClient({
projectId: "your-project-id",
apiKey: "vk_pub_xxx", // the publishable key: safe in a browser
});
Option Meaning
projectId Your project ID. Data, auth and realtime need it
apiKey A publishable key (vk_pub_…) in browsers and apps, a secret key (vk_srv_…) only on servers. See API key types
baseUrl The API’s address. Defaults to https://api.vaif.studio
accessToken The signed-in user’s token, as a string or a function that returns one. Row rules use it. vaif.setAuth(token) changes it later
timeout Per-request timeout in milliseconds (default 30000)
retry The retry policy (below)
realtime Options for the shared realtime connection, such as your own WebSocket implementation
refreshTokenMode Where a signed-in user’s refresh token lives: "auto" (default), "body" or "cookie"

In a browser, the client warns in the console when it is given a vk_srv_ key, because that key bypasses row rules.

type Note = { id: string; title: string; n: number; done: boolean; created_at: string };
const notes = vaif.from<Note>("notes");
const { data: created } = await notes.create({ title: "Buy milk", n: 1, done: false });
const { data: page } = await vaif
.from<Note>("notes")
.eq("done", false)
.orderBy("created_at", "desc")
.limit(20)
.list();
await notes.update(created.id, { done: true });
await notes.delete(created.id);
const open = await vaif.from<Note>("notes").eq("done", false).count();
console.log(page.length, open);

Filters are eq, neq, gt, gte, lt, lte, like, ilike and in, and chained filters must all match. select(...columns) limits the columns returned. single() and maybeSingle() return { data, error } instead of throwing. More in Querying data.

await vaif.auth.signUp("sam@example.com", "a-long-unique-passphrase");
const result = await vaif.auth.login("sam@example.com", "a-long-unique-passphrase");
if ("challengeToken" in result) {
// Two-factor sign-in is on for this user: ask for the 6-digit code.
const session = await vaif.auth.mfa.verify(result.challengeToken, "123456");
vaif.setAuth(session.accessToken);
} else if ("accessToken" in result) {
vaif.setAuth(result.accessToken);
}

signUp and requestPasswordReset are for the users of your own app, so the client needs your projectId. VAIF Studio accounts are created in the console: on a client without projectId, both methods throw VaifConsoleOnlyError (code: "CONSOLE_ONLY") before sending anything.

The SDK does not store tokens. Keep the session where your app keeps secrets, or use the React or Expo provider, which stores and refreshes it for you. More in Authentication.

import { upload } from "@vaifstudio/client/storage";
const { key, sizeBytes } = await upload(vaif, "avatars", "users/42/me.jpg", file, {
onProgress: (e) => console.log(`${Math.round(e.fraction * 100)}%`),
});
const url = await vaif.storage.createSignedUrl(key, 3600, { bucket: "avatars" });
console.log(sizeBytes, url);

upload() switches to a multipart upload for large files and can resume after a crash; see Uploads in depth. More in File storage.

const result = await vaif.functions.invoke<{ sent: boolean }>("send-email", {
body: { to: "sam@example.com", subject: "Welcome" },
});
console.log(result.status, result.data.sent);

invoke is a POST and is not retried unless you opt in with { retry: { maxRetries } }. Opt in only for functions that are safe to run twice.

const subscription = vaif
.from("messages")
.eq("room_id", "room-42")
.on("INSERT", (event) => console.log(event.new))
.subscribe();
// Later
subscription.unsubscribe();

Row changes, broadcast and presence share one authenticated connection per client. More in Realtime.

Import What it contains
@vaifstudio/client The client: data, auth, storage, functions and realtime, plus the error classes
@vaifstudio/client/storage upload() with multipart, progress, cancellation and localStorageResumeStore()
@vaifstudio/client/storage/browser Browser helpers: uploadFromInput() and createDropZone()
@vaifstudio/client/operations createOperationsClient(): one typed method for every other API route

Every failure is a VaifError subclass with code, statusCode and requestId:

Class When
VaifAuthError 401 or 403, or a refused realtime connection
VaifValidationError 400 or 422
VaifNotFoundError 404
VaifConflictError 409
VaifRateLimitError 429, with retryAfter in seconds
VaifNetworkError, VaifTimeoutError No response, or the request timed out
VaifNotImplementedError A method this API does not serve
VaifConsoleOnlyError Account sign-up or reset, which only the console does
import { isVaifNotFoundError } from "@vaifstudio/client";
try {
await vaif.from("notes").get("missing-id");
} catch (error) {
if (isVaifNotFoundError(error)) console.log("no such note");
else throw error;
}

Only GET, HEAD and OPTIONS requests are retried automatically, up to 3 times on 500, 502, 503 and 504 responses and on network errors, with exponential backoff. A 429 is retried at most once, never before its Retry-After. Writes are never replayed. See Error codes for the error body every response uses.