Skip to main content

JavaScript SDK

@taruvi/sdk is the TypeScript client for TaruviBase. Use it in browser apps, JavaScript servers, and React Native. You create one Client with your site address and app slug, then pass it to service classes such as Database, Storage, and Functions.

This guide covers @taruvi/sdk 1.5.4. It takes you from install to a first query, then explains how requests, responses, and errors work.

Before you begin#

You need:

  • A TaruviBase app. Set one up if you don't have one.
  • The app's TARUVI_SITE_URL and TARUVI_APP_SLUG from Settings → Connect in TaruviBase Console.
  • A project that uses ES modules. The package ships ES modules with its own type declarations; there is no separate @types package.
  • For server code, Node.js 20.10 or later.

Install the SDK#

npm install @taruvi/sdk

npm 7 and later also install the SDK's one peer dependency, axios.

Create a client#

Create the client once, when your application starts, and reuse it. Where the user's session comes from depends on where your code runs:

src/taruvi.ts
import {Client} from '@taruvi/sdk';

export const taruvi = new Client({
apiUrl: import.meta.env.VITE_TARUVI_SITE_URL,
appSlug: import.meta.env.VITE_TARUVI_APP_SLUG,
});

This example reads Vite's public build variables. Use your framework's equivalent, such as NEXT_PUBLIC_* in Next.js.

After a user signs in, TaruviBase returns them to your app with #session_token=… in the address. Constructing the client stores that token in localStorage, removes it from the address bar, and sends it as X-Session-Token on every request. Add sign-in to complete the flow.

The constructor throws an Error when apiUrl is empty. Every request identifies the SDK with an X-Taruvi-Client header, such as taruvi-js/1.5.4 (browser), which helps TaruviBase support trace a failing call.

Make your first request#

This query reads the first 20 open tasks from a tasks table, sorted by title. It uses the browser client above and a signed-in user; the quickstart creates the table.

src/tasks.ts
import {Database} from '@taruvi/sdk';
import {taruvi} from './taruvi';

type Task = {id: string; title: string; done: boolean};

const database = new Database(taruvi);

export async function listOpenTasks(): Promise<Task[]> {
const response = await database
.from<Task>('tasks')
.filters('done', 'eq', false)
.sort('title', 'asc')
.pageSize(20)
.execute();

return response.data as Task[];
}

execute() sends GET /api/apps/APP_SLUG/datatables/tasks/data/?done=false&ordering=title&page_size=20 and resolves with the response envelope.

How requests work#

Builders are immutable. from(), filters(), sort(), and the other query methods each return a new builder. Nothing is sent until you call execute(), first(), or count(), so you can keep a base query and extend it:

const tasks = database.from<Task>('tasks');
const open = tasks.filters('done', 'eq', false);
open.sort('title', 'asc').pageSize(10);

Call the operation last. get(), create(), update(), upsert(), delete(), and their bulk variants choose the HTTP operation. A query method called after one of them starts a new read, so put the operation right before execute():

const taskId = 'TASK_ID';

await database.from('tasks').create({title: 'Ship docs', done: false}).execute();
await database.from('tasks').get(taskId).update({done: true}).execute();
await database.from('tasks').delete(taskId).execute();

get(id) before update() selects the record, and delete(id) takes the ID itself. Neither sends a request until execute(). A builder used wrongly, such as update() without a record ID, throws a plain Error before anything is sent.

Set a page size. A list read without pageSize() returns every matching row, and page() has no effect without it. The largest page size is 1,000 by default; a larger pageSize() fails with ValidationError rather than being reduced for you.

Services share the client. Construct each service with the same client: new Database(taruvi), new Storage(taruvi), new Functions(taruvi). The method reference lists every service.

Read the response#

Most calls resolve with the TaruviBase response envelope:

{
"status": "success",
"message": "Data retrieved successfully",
"data": [{"id": "RECORD_ID", "title": "Ship docs", "done": false}],
"total": 1
}
CallResolves with
List read: execute() without get()The envelope; data is an array and total counts the whole result
get(id).execute()The envelope; data is one record
create(...).execute()The envelope; data is an array, even for one record
update(...).execute() after get(id)The envelope; data is the updated record
bulkUpdate(...).execute() or upsert(...).execute()The envelope; data is {records, count}
delete(id).execute()An empty body
bulkDelete(), delete(ids), or deleteFiltered(), then execute(){deleted_count, message}, without the envelope. When nothing matches deleteFiltered(), the envelope with data: {deleted_count: 0}
first()The first record, or null. It requests a single row
count()A number: the total matching rows. It requests a single row

Handle errors#

A failed request rejects with a TaruviError subclass. Each error carries the HTTP statusCode and the platform's error code, plus detail and field-level errors when the platform sends them:

src/create-task.ts
import {
Auth,
AuthError,
BillingError,
Database,
ForbiddenError,
NetworkError,
RateLimitError,
TaruviError,
ValidationError,
} from '@taruvi/sdk';
import {taruvi} from './taruvi';

const auth = new Auth(taruvi);
const database = new Database(taruvi);

export async function createTask(title: string) {
try {
return await database.from('tasks').create({title, done: false}).execute();
} catch (error) {
if (error instanceof ValidationError) {
console.warn('Fix these fields:', error.errors);
} else if (error instanceof AuthError) {
auth.login();
} else if (error instanceof ForbiddenError) {
console.warn('Signed in, but not allowed:', error.detail);
} else if (error instanceof BillingError) {
console.warn(`Billing blocked ${error.module ?? 'this request'}:`, error.message);
} else if (error instanceof RateLimitError) {
console.warn(`Retry in ${error.retryAfter ?? 1} seconds`);
} else if (error instanceof NetworkError) {
console.warn('TaruviBase could not be reached');
} else if (error instanceof TaruviError) {
console.error(error.code, error.message);
}
throw error;
}
}
  • 401, 410, and 419 reject with AuthError and clear the stored session, because it can no longer be used. 403 keeps it: the user is signed in but lacks permission.
  • Billing refusals reject with BillingError when the organization's billing blocks the request. Only gate_unavailable is worth retrying; check error.retryable.
  • The SDK does not retry. Retry reads yourself. Before you retry a write, read the current state so the change isn't applied twice.
  • Failures without an HTTP response, such as DNS errors or a browser blocking the request under CORS, reject with NetworkError and a statusCode of 0. See Allow your app's address.

The error reference maps every class to its status and code. API request lifecycle explains the order in which TaruviBase checks a request.

Package compatibility#

  • @taruvi/sdk 1.5.4 has one peer dependency: axios 1.x.
  • Server code needs Node.js 20.10 or later, for JSON import attributes.
  • The SDK and the TaruviBase API are versioned separately. Pin the SDK version, then test sign-in, reads, writes, and a permission failure before you upgrade.

Next steps#