Querying data
vaif.from(table) returns a query builder for one table. Filters, sorting and paging chain; the method at the end (list(), get(), create() and so on) sends the request to the data API at /v1/data/{projectId}/{table}.
Give the builder a row type to get typed results:
import { createVaifClient } from "@vaifstudio/client";
const vaif = createVaifClient({ baseUrl: "https://api.vaif.studio", projectId: process.env.VAIF_PROJECT_ID, apiKey: process.env.VAIF_PUBLISHABLE_KEY,});
type Post = { id: string; title: string; author_id: string; published: boolean; views: number; created_at: string;};
const posts = vaif.from<Post>("posts");// Many rowsconst { data, count } = await posts.eq("published", true).orderBy("created_at", "desc").limit(20).list();
// One row by primary key (throws VaifNotFoundError if it does not exist)const { data: post } = await posts.get("0b9d3f4e-6c1a-4d2b-9a7e-2f1c8e5d3a10");
// The first match, or nullconst newest = await posts.orderBy("created_at", "desc").first();
// Exactly one row, as { data, error } instead of throwingconst { data: one, error } = await posts.eq("title", "Hello").maybeSingle();
console.log(data.length, count, post.title, newest?.title, one, error);list() returns { data, count, hasMore }. It returns 50 rows unless you set limit(), and at most 1000. single() returns an error when no row or several rows match; maybeSingle() returns data: null when no row matches.
Filters
Section titled “Filters”| Method | Matches rows where |
|---|---|
.eq(column, value) |
the column equals the value |
.neq(column, value) |
the column does not equal the value |
.gt(), .gte(), .lt(), .lte() |
the column is greater than, at least, less than, or at most the value |
.like(column, pattern) |
the column matches a SQL LIKE pattern (% is a wildcard) |
.ilike(column, pattern) |
the same, ignoring case |
.in(column, values) |
the column is one of the values |
.where(filter) |
any filter, including AND and OR groups |
Chained filters are combined with AND. Use .where() for OR:
const { data: popularOrMine } = await posts .where({ OR: [ { field: "author_id", operator: "eq", value: "user-1" }, { field: "views", operator: "gte", value: 1000 }, ], }) .list();console.log(popularOrMine);Sort, page and pick columns
Section titled “Sort, page and pick columns”// Sort by several columnsconst sorted = await posts.orderBy("published", "desc").orderBy("created_at", "desc").list();
// Page numbersconst page2 = await posts.orderBy("created_at", "desc").paginate({ page: 2, pageSize: 20 });
// Offsetsconst next = await posts.limit(20).offset(40).list();
// Only some columnsconst titles = await posts.select("id", "title").list();
console.log(sorted.data, page2.pageInfo, next.data, titles.data);paginate() returns the rows and pageInfo: total, page, pageSize, pageCount, hasNextPage and hasPrevPage.
Create, update and delete
Section titled “Create, update and delete”const { data: created } = await posts.create({ title: "Hello", author_id: "user-1", published: false, views: 0, created_at: new Date().toISOString(),});
const { data: updated } = await posts.update(created.id, { published: true });
await posts.delete(created.id);
// Insert, or update the row with the same title if one existsawait posts.upsert( { title: "Hello", author_id: "user-1", published: true, views: 0, created_at: new Date().toISOString() }, { conflictFields: ["title"] });
console.log(updated.published);upsert() updates every column except the conflict columns, which must have a unique constraint. For many rows at once, see Batch operations.
Counts and single aggregates
Section titled “Counts and single aggregates”const total = await posts.count();const published = await posts.eq("published", true).count();const totalViews = await posts.sum("views");const averageViews = await posts.avg("views");
console.log(total, published, totalViews, averageViews);min() and max() work the same way. Each call computes one aggregate, and takes equality filters only.
Not available
Section titled “Not available”These methods exist on the builder but throw a VaifError with the code NOT_SUPPORTED, because the data API does not offer them:
| Method | Use instead |
|---|---|
.distinct() |
Remove duplicates in your code |
.aggregate([...]) (several at once) |
count(), sum(), avg(), min() and max(), one at a time |
.cursorPaginate() |
paginate({ page, pageSize }) |
.raw(), .transaction() |
See Batch operations |
Large numbers
Section titled “Large numbers”numeric and bigint values can arrive as strings, because they can be larger than JavaScript numbers hold exactly. Register the column types, and the client converts them to number, or to BigInt when a value does not fit:
vaif.setColumnTypes("orders", { amount: "numeric", item_count: "bigint" });Row rules
Section titled “Row rules”Every read and write goes through your project’s row rules. Send the signed-in user’s access token, so rules that depend on the user apply.