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_URLandTARUVI_APP_SLUGfrom 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
@typespackage. - 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:
- Browser
- Node.js server
- React Native
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.
import {Client} from '@taruvi/sdk';
// Create one client per signed-in user. On a server, the session token is
// held by this client instance only.
export function taruviForSession(sessionToken: string): Client {
return new Client({
apiUrl: process.env.TARUVI_SITE_URL!,
appSlug: process.env.TARUVI_APP_SLUG!,
token: sessionToken,
});
}
The SDK sends the token as X-Session-Token. Do not share one client between
users: every request it sends carries that user's permissions.
For jobs and services that don't act for a user, create the client with
authMode: 'apiKey'; see Call TaruviBase from a server.
For Next.js and other server-rendered apps, see
Server-rendered apps.
import {Client} from '@taruvi/sdk';
export function taruviForSession(sessionToken: string): Client {
return new Client({
apiUrl: 'https://YOUR_SITE_HOST',
appSlug: 'APP_SLUG',
token: sessionToken,
});
}
YOUR_SITE_HOST and APP_SLUG are the values from Settings → Connect.
In React Native the SDK behaves as it does on a server: it keeps the token
you pass on the client instance, and it neither reads a sign-in redirect nor
uses localStorage. Auth.login() can't open a sign-in screen there; it logs
an error and does nothing. Your app obtains the user's session token itself and
keeps it in the device's secure storage.
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.
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
}
| Call | Resolves 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:
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, and419reject withAuthErrorand clear the stored session, because it can no longer be used.403keeps it: the user is signed in but lacks permission.- Billing refusals reject with
BillingErrorwhen the organization's billing blocks the request. Onlygate_unavailableis worth retrying; checkerror.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
NetworkErrorand astatusCodeof0. 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/sdk1.5.4 has one peer dependency:axios1.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.