JavaScript SDK reference
This page lists the public API of @taruvi/sdk 1.5.4: each service, its
methods, and the HTTP request each method sends. Every service takes the same
Client in its constructor. Request paths are relative to your site address,
and APP_SLUG is the client's appSlug.
For setup and how requests work, start with JavaScript SDK.
Client#
new Client(options) holds your site address, your app, and how requests
authenticate. Create one and pass it to every service.
import {Client} from '@taruvi/sdk';
const taruvi = new Client({
apiUrl: 'https://YOUR_SITE_HOST',
appSlug: 'APP_SLUG',
});
Options
apiUrl(required) — Your site address,TARUVI_SITE_URL. A trailing slash is removed.appSlug(required) — The app's slug,TARUVI_APP_SLUG.authMode—'session', the default, sends the signed-in user's session.'apiKey'sendsapiKeyinstead; it works only on a server and throws in browsers and React Native.apiKey— An API key, sent asAuthorization: Api-Key …. Required withauthMode: 'apiKey'and ignored otherwise. Releases before 1.5.4 required a placeholder here.token— A session token, for Node.js and React Native. Ignored in the browser, which reads the token from the sign-in redirect.deskUrl— The host of the hosted sign-in pages. Defaults toapiUrl.detectSessionInUrl— Whether creating the client captures#session_tokenfrom the address. Defaults totrue. Server-rendered apps set it tofalseand callhandleRedirect().
getConfig() returns a copy of the options. The constructor throws an Error
when apiUrl is empty, and logs the SDK version with console.info. Every
request sends X-Taruvi-Client: taruvi-js/VERSION (RUNTIME), where RUNTIME
is browser, server, or react-native.
Auth#
new Auth(client) sends users to hosted sign-in and manages their session. For
the whole flow, see Authenticate with the JavaScript SDK.
import {Auth} from '@taruvi/sdk';
const auth = new Auth(taruvi);
Sign in and sign up#
login(callbackUrl?) and signup(callbackUrl?) send the browser to
TaruviBase's hosted sign-in or sign-up page, at /accounts/login/ or
/accounts/signup/ on deskUrl. When the user finishes, TaruviBase returns
them to callbackUrl, which defaults to the current page without its query
string.
auth.login(`${window.location.origin}/dashboard`);
Outside a browser, both log an error and do nothing.
Sign out#
logout(callbackUrl?) forgets the stored session. In a browser, it then sends
the user to /accounts/logout/ on deskUrl, which returns them to
callbackUrl or, by default, your app's origin. If TaruviBase doesn't accept
that address, the user confirms sign-out on TaruviBase's page instead.
await auth.logout();
To forget the session without leaving the page, use
clearSession().
Check the session#
if (!(await auth.isUserAuthenticated())) {
auth.login();
}
hasToken()— Whether a session token is stored. Sends no request.getSessionToken()— The stored session token, ornull.isUserAuthenticated()— Resolvestruewhen TaruviBase accepts the stored session, andfalseotherwise.validateSession()— The same check, but rejects withAuthErrorwhen the session isn't valid.
Handle the sign-in redirect#
handleRedirect(url?) stores the session token that hosted sign-in added to
the address, removes the sign-in values from the address bar, and returns the
token, or null when there is none. Call it on the page users return to when
you create the client with detectSessionInUrl: false.
auth.handleRedirect();
Set or clear the session#
setSession(token)— Uses a session token your app got elsewhere, such as a cookie, for later requests.clearSession()— Forgets the stored session without redirecting. To also end it on TaruviBase, uselogout().
Get the current user#
const me = await auth.getCurrentUser();
console.log(me?.data.username);
Returns TaruviResponse<UserData>, or null when no session is stored or
the request fails.
Database#
new Database(client).from<T>(table) starts a query on a table, where T
types its records. Builder methods return a new builder, and nothing is sent
until you call execute(), first(), or count(). Request paths below are
relative to /api/apps/APP_SLUG/datatables/TABLE/.
For a PostgreSQL range column in your record type, use the exported
PgRangeValue type. It has lower, upper, bounds, and empty fields.
import {Database} from '@taruvi/sdk';
type Task = {id: string; title: string; done: boolean};
const database = new Database(taruvi);
Call an operation such as get(), create(), or delete() last, right before
execute(). A builder used wrongly, such as update() without get(id),
throws a plain Error before anything is sent.
List records#
Reads the records that match the query. Always set a page size: without
pageSize(), a list read returns every matching row.
await database
.from<Task>('tasks')
.sort('title', 'asc')
.page(1)
.pageSize(20)
.execute();
sort(field, order?)— Sorts byfield, ascending unlessorderis'desc'. Call it again to add a tie-breaker, or pass[{field, order}]to set several at once. Sent asordering.pageSize(n)— Records per page, up to 1,000 by default; a larger value is rejected withValidationError. Sent aspage_size.page(n)— The page number, starting at 1. Takes effect only withpageSize().
Returns the response envelope: data is the page of records, and total
counts every match.
Filter records#
filters(field, operator, value) adds a condition. eq sends the bare field
name, and other operators send field__operator. Array values are joined with
commas, and repeating a field and operator replaces the earlier value.
await database
.from<Task>('tasks')
.filters('done', 'eq', false)
.filters('title', 'icontains', 'docs')
.execute();
For or conditions and nesting, pass a filter tree instead. It's sent as JSON
in filters, and TaruviBase combines it with any flat filters using AND:
await database
.from<Task>('tasks')
.filters([
{
operator: 'or',
value: [
{field: 'done', operator: 'eq', value: false},
{field: 'title', operator: 'icontains', value: 'urgent'},
],
},
])
.execute();
operator is a FilterOperator.
Choose a filter operator
explains each one:
| Purpose | Operators |
|---|---|
| Compare | eq, ne, gt, gte, lt, lte |
| Match a set | in, nin, ina, nina |
| Match text | contains, ncontains, icontains, nicontains, containss, ncontainss, startswith, nstartswith, startswiths, nstartswiths, endswith, nendswith, endswiths, nendswiths, like, ilike, search |
| Match a range | between, nbetween |
| Match null | null, nnull |
| Array fields | acontains, nacontains, acontainedby, nacontainedby, aoverlap, naoverlap, aelement, naelement |
| Range fields | rcontains, rcontainedby, roverlaps, radjacent, rstrictleft, rstrictright |
Expand related records#
await database
.from<Task>('tasks')
.populate(['assignee', 'project'])
.execute();
populate(fields)— Expands the named relationships.populateAll()— Expands every first-level relationship, withpopulate=*.
See Relationships.
Search and choose columns#
await database
.from<Task>('tasks')
.search('invoice')
.fields('id,title')
.execute();
search(query)— Full-text search, on tables that have a search vector.fields(columns)— The columns to return, comma-separated.
Aggregate#
await database
.from('tasks')
.aggregate('count(*)')
.groupBy('done')
.having('count__gte=10')
.execute();
aggregate(...expressions)— Aggregates such as'count(*)'or'sum(total)'. Sent as_aggregate.groupBy(...fields)— Groups the results. Sent as_group_by.having(condition)— Filters the groups by an aggregate alias, such as'count__gte=10'. Sent as_having.
See Aggregations.
Include allowed actions#
await database
.from<Task>('tasks')
.allowedActions(['update', 'delete'])
.execute();
Each row gains _allowed_actions: the ones among update and delete that the
signed-in user may perform on it. It can't be combined with aggregation.
Get a record#
await database.from<Task>('tasks').get('TASK_ID').execute();
Returns the envelope, with the record in data. A missing record rejects
with NotFoundError.
Get the first record or a count#
await database.from<Task>('tasks').filters('done', 'eq', false).first();
await database.from<Task>('tasks').filters('done', 'eq', false).count();
first()— The first matching record, ornull. It requests a single row.count()— The number of matching rows. It requests a single row and readstotal.
Create records#
await database
.from<Task>('tasks')
.create([
{title: 'Write release notes', done: false},
{title: 'Ship docs', done: false},
])
.execute();
Pass one record or an array. Returns the envelope; data is an array of
the created records, even for one.
Upsert records#
Inserts records, or updates those that already exist.
await database
.from<Task>('tasks')
.upsert([{title: 'Ship docs', done: true}], ['title'])
.execute();
body(required) — One record or an array.uniqueFields— The columns that identify an existing record. Defaults to the primary key.
Returns the envelope; data is {records, count}.
Update records#
get(id) selects a record and update() sends the changes:
await database
.from<Task>('tasks')
.get('TASK_ID')
.update({done: true})
.execute();
Returns the envelope, with the updated record in data.
bulkUpdate(records) updates several records in one request. Each record
includes its primary key, and data is {records, count}:
await database
.from<Task>('tasks')
.bulkUpdate([
{id: 'TASK_ID_1', done: true},
{id: 'TASK_ID_2', done: true},
])
.execute();
Delete records#
await database.from('tasks').delete('TASK_ID').execute();
delete(id)— Deletes one record. The response body is empty.bulkDelete(ids)— Deletes records by ID, withDELETE …/data/?ids=. Resolves with{deleted_count, message}.deleteFiltered()— Deletes every record that matches the query's filters, withDELETE …/data/?filter=. Throws when no filter is set. When nothing matches, it resolves with the envelope anddata: {deleted_count: 0}.
deleteFiltered() removes every matching record in one request and can't be
undone. Run the same filters as a read first and check total before you
delete.
Graphs and edges#
For tables with parent–child or graph relationships, these methods read a record together with its related records:
await database
.from('folders')
.get('ROOT_ID')
.include('descendants')
.depth(2)
.format('tree')
.execute();
include(direction)—'descendants','ancestors', or'both'.depth(n)— The maximum traversal depth.format(format)—'flat','tree', or'graph'.types(types)— The relationship types to follow. Sent asrelationship_type.
edges() targets the table's edge table, TABLE_edges. create(), update(),
and delete() then act on edges, and delete(ids) with an array of numbers
deletes several:
await database
.from('folders')
.edges()
.create([{from_id: 'PARENT_ID', to_id: 'CHILD_ID', type: 'contains'}])
.execute();
Storage#
new Storage(client).from(bucket) selects a bucket; every method below runs on
it. Object paths may contain /, and each segment is URL-encoded. Paths in the
request lines are relative to
/api/apps/APP_SLUG/storage/buckets/BUCKET/objects/.
import {Storage} from '@taruvi/sdk';
import type {
StorageAccessLinkResponse,
StorageBrowseResponse,
StorageDeleteBatchResponse,
StorageListResponse,
StorageResponse,
StorageUploadBatchResponse,
TaruviResponse,
} from '@taruvi/sdk';
const storage = new Storage(taruvi).from('documents');
execute() resolves with a union of response types. Pass the one you expect,
as the examples do, to narrow it; every response type is exported from
@taruvi/sdk. For the full workflow, see Storage objects.
List objects#
Lists the objects in the bucket that match filter(). Without filter(),
execute() lists every object.
await storage
.filter({
prefix: 'reports/',
mimetype: 'application/pdf',
page: 1,
page_size: 50,
})
.execute<StorageListResponse>();
Common filters
prefix— Only objects under this path.search— Match file names.mimetype— An exact type, such asapplication/pdf.mimetype_category—image,video,audio,application, ortext.visibility—publicorprivate.page,page_size— Pagination.
StorageFilters also covers size and date ranges and metadata search; see
Storage filters.
Returns StorageListResponse: data is a list of StorageObject, and
total counts every match.
Browse folders#
Lists the folders and files directly under a prefix, like a file manager.
await storage
.browse({prefix: 'reports/', sort: 'name', order: 'asc'})
.execute<StorageBrowseResponse>();
Options
prefix— The folder to open. Omit it for the root.sort—name,size,created_at, orupdated_at.order—ascordesc.page,page_size— Pagination.page_sizedefaults to 50, up to 100.
Returns StorageBrowseResponse: data.folders, data.objects, and
data.has_next.
Upload files#
Uploads up to 100 files, 100 MB in total, in one request.
await storage
.upload({
files: [file],
paths: ['reports/q3.pdf'],
metadatas: [{owner: 'finance'}],
})
.execute<StorageUploadBatchResponse>();
Parameters
files(required) — TheFileobjects to upload.paths(required) — The object path for each file, in the same order.metadatas(required) — Metadata for each file, in the same order. Pass{}for none.
Returns StorageUploadBatchResponse. A file that fails doesn't reject the
call: data.uploaded_count says how many succeeded, and data.failed lists
the rest with the reason.
Download a file#
await storage.download('reports/q3.pdf').execute<Blob>();
Returns a Blob.
Read an object's details#
Reads an object's details without downloading it.
await storage
.metadata('reports/q3.pdf')
.execute<StorageResponse>();
Returns StorageResponse: data is a StorageObject with file_path,
size, mimetype, metadata, visibility, and timestamps.
Update metadata#
Replaces an object's metadata.
await storage
.update('reports/q3.pdf', {metadata: {owner: 'finance', status: 'final'}})
.execute<StorageResponse>();
Parameters
path(required) — The object path.metadata— The new metadata, which replaces the old. Up to 2 KB.
Returns StorageResponse.
An object's visibility always follows its bucket. TaruviBase ignores a
visibility sent here; to change it, update the bucket.
Delete objects#
await storage
.delete(['reports/q1.pdf', 'reports/q2.pdf'])
.execute<StorageDeleteBatchResponse>();
Returns StorageDeleteBatchResponse. Objects that are missing, or that a
policy doesn't let the user delete, don't reject the call: data.deleted_count
says how many were deleted, and data.failed lists the rest.
Get an Office link#
For buckets backed by SharePoint, viewAccess(path) and editAccess(path)
return a link that opens an Office file for viewing or editing. editAccess()
always needs a signed-in user.
const {data} = await storage
.editAccess('contracts/msa.docx')
.execute<TaruviResponse<StorageAccessLinkResponse>>();
window.open(data.url);
Returns TaruviResponse<StorageAccessLinkResponse>: data.url and
data.mode.
Build a download URL#
getUrl(path) returns an object's download URL without sending a request.
Private objects still need an authenticated request to that URL.
storage.getUrl('reports/q3.pdf');
Functions#
new Functions(client) runs your app's functions.
Run a function#
import {Functions} from '@taruvi/sdk';
const functions = new Functions(taruvi);
await functions.execute<{sent: number}>('send-report', {
params: {month: '2026-09'},
});
slug(required) — The function's slug.params— The function's input.async—truequeues the run and returns right away;falsewaits for the result. Leave it out to use the function's own mode.
Returns FunctionResponse<T>: data holds the function's return value,
and invocation describes the run. A queued run returns an empty list in
data; invocation.celery_task_id identifies it. See
Execute functions.
Secrets#
new Secrets(client) reads secrets that the signed-in user may see.
import {Secrets} from '@taruvi/sdk';
import type {SecretResponse} from '@taruvi/sdk';
const secrets = new Secrets(taruvi);
Get a secret#
await secrets.get('app_config').execute<SecretResponse>();
key(required) — The secret's key.app— An app slug, to read that app's secret.tags— Tags the secret must have; if it has none of them, the call rejects withNotFoundError.
Returns SecretResponse once you pass the type to execute(): data
holds the key, value, and details.
Get several secrets#
await secrets.list(['app_config', 'feature_flags']);
keys(required) — Up to 100 keys.app— An app slug, to read that app's secrets.includeMetadata— Return{value, tags, secret_type}for each key instead of the value alone.
Returns the envelope; data maps each key to its value. Keys that are
missing, or that the user may not read, are left out.
Analytics#
new Analytics(client) runs saved queries.
Run a saved query#
import {Analytics} from '@taruvi/sdk';
const analytics = new Analytics(taruvi);
await analytics.execute<{month: string; total: number}[]>(
'revenue-by-month',
{params: {year: 2026}},
);
querySlug(required) — The saved query's slug.params— Values for the query's parameters.
Returns AnalyticsResponse<T>, with the query's result in data. See
Execute saved queries.
A run that fails throws TaruviError with statusCode 400 and code
BAD_REQUEST; the cause is in message. See
Error categories.
Policy#
new Policy(client) asks TaruviBase what the signed-in user may do. Checks
always run as that user.
import {Policy} from '@taruvi/sdk';
const policy = new Policy(taruvi);
Check resources#
Checks several actions on several records in one request.
const {results} = await policy.checkResource([
{
resource: 'datatable:tasks',
recordId: 'TASK_ID',
attributes: {},
actions: ['update', 'delete'],
},
]);
const canDelete = results[0]?.actions.delete === 'EFFECT_ALLOW';
Each entry takes the policy resource kind, the recordId, the record's
attributes (pass {} for none), and the actions to check.
Returns PolicyCheckBatchResult: one result per entry, mapping each action
to EFFECT_ALLOW or EFFECT_DENY.
List allowed actions#
await policy.getAllowedActions(
{kind: 'datatable:tasks', id: 'TASK_ID', attr: {}},
{actions: ['read', 'update', 'delete']},
);
resource(required) —{kind, id, attr}.actions— The actions to check. Defaults toread,write,create,update, anddelete.auxData— Extra data for the policy.
Returns the names of the allowed actions. The principal option is
deprecated: TaruviBase rejects an explicit principal with a 400.
See Runtime permission checks.
User#
new User(client) manages your site's users. Any signed-in user can list and
read them. Creating, updating, and deleting users needs organization access (an organization owner, admin, or member, or an API key one of them created) or a Super Admin
app role in one of the site's apps. Anyone else gets 403 with code
FORBIDDEN, which rejects with ForbiddenError. A Super Admin app role can't
change superusers or members of your organization. See
Site users.
import {User} from '@taruvi/sdk';
const users = new User(taruvi);
List users#
await users.list({search: 'ada', is_active: true, page_size: 20});
Filters: search, is_active, is_superuser, is_deleted, roles
(comma-separated role slugs), ordering, page, and page_size. Without
filters, the list returns active users that aren't deleted, and leaves out
TaruviBase cloud users and superusers. A filter you pass replaces its default:
is_active: false lists inactive users.
Returns UserListResponse, with data and total.
Get a user#
await users.getUser('ada');
Returns UserResponse.
Create a user#
await users.createUser({
username: 'ada',
password: 'CHOOSE_A_PASSWORD',
confirm_password: 'CHOOSE_A_PASSWORD',
first_name: 'Ada',
last_name: 'Lovelace',
role_slugs: ['editor'],
});
username, email, password, confirm_password, first_name, and
last_name are required. is_active, attributes, and role_slugs are
optional. TaruviBase ignores is_staff and is_cloud_user.
Returns UserResponse.
Update a user#
await users.updateUser('ada', {first_name: 'Augusta'});
Changes only the fields you pass: username, email, first_name,
last_name, or is_active. TaruviBase ignores is_staff. Returns
UserResponse.
Delete a user#
await users.deleteUser('ada');
Soft-deletes the user: TaruviBase marks them deleted and inactive, and lists
them only when you filter with is_deleted: true. You can't delete
yourself.
List a user's apps#
await users.getUserApps('ada');
Returns UserAppsResponse: each app's name, slug, display_name,
icon, and url.
Assign and revoke roles#
await users.assignRoles({
roles: ['editor'],
usernames: ['ada'],
expires_at: '2026-12-31T23:59:59Z',
});
assignRoles({roles, usernames, expires_at?})— Gives every user inusernamesevery role inroles, untilexpires_atwhen it's set.revokeRoles({roles, usernames})— Removes them, withDELETE /api/revoke/roles/.
Both need organization access (an organization owner, admin, or member, or an API key one of them created), and take up to 100 roles and 100 usernames. Assignments that
already exist, or when revoking don't exist, are skipped and count as
success. Returns RolesResponse: a message, plus data.failures
when some changes failed, each with username, role, and error.
Read and update preferences#
await users.updatePreferences({theme: 'dark', timezone: 'Europe/London'});
getPreferences() reads the signed-in user's preferences with
GET /api/users/me/preferences/. updatePreferences() changes only the
fields you pass: date_format, time_format, timezone, theme, or
widget_config. Both return UserPreferencesResponse.
App#
new App(client) reads details of the client's app.
import {App} from '@taruvi/sdk';
import type {AppSettingsResponse, RolesListResponse} from '@taruvi/sdk';
const app = new App(taruvi);
List roles#
await app.roles().execute<RolesListResponse>();
Returns RolesListResponse once you pass the type to execute().
Read app settings#
await app.settings().execute<AppSettingsResponse>();
Returns AppSettingsResponse: the app's display name, colors, icon,
banner, and support details.
Settings#
new Settings(client) reads site-wide settings.
import {Settings} from '@taruvi/sdk';
const settings = new Settings(taruvi);
Read public site settings#
await settings.get<{
domain: string;
settings: Record<string, unknown>;
}>();
Works without a signed-in user. Returns the site's domain and its public
settings, such as theme and branding.
Read and update the user-attribute schema#
await settings.getUserAttributes();
updateUserAttributes(schema) replaces the schema, a JSON Schema, with
POST /api/settings/user-attributes/. Changing it needs organization access. See
User attributes.
Errors#
Every failed request rejects with a TaruviError or one of its subclasses.
| Class | statusCode | code |
|---|---|---|
ValidationError | 400 | VALIDATION_ERROR |
AuthError | 401, 410, or 419 | UNAUTHORIZED |
BillingError | 402, 429, or 503 | account_suspended, product_suspended, or gate_unavailable |
ForbiddenError | 403 | FORBIDDEN |
NotFoundError | 404 | NOT_FOUND |
ConflictError | 409 | CONFLICT |
RateLimitError | 429 | RATE_LIMITED |
TimeoutError | 504 | GATEWAY_TIMEOUT |
NetworkError | 0 | NETWORK_ERROR |
TaruviError | Any other status | The platform's code, or INTERNAL_ERROR when it sends none |
Every error carries message, and detail when the platform sends it.
ValidationError and TaruviError add errors, the problems by field.
TaruviError also has data. RateLimitError adds retryAfter, in seconds,
from the Retry-After header. ErrorCode exports the code strings.
BillingError means the organization's billing blocked the request: the
account isn't active (account_suspended), the plan's usage for module is
used up for this period (product_suspended), or billing status couldn't be
read (gate_unavailable). Only gate_unavailable is worth retrying; check
retryable. A 429 with product_suspended is a BillingError, not a
RateLimitError.
Enums#
| Export | Values |
|---|---|
SortOrder | ASC 'asc', DESC 'desc' |
DataFormat | FLAT 'flat', TREE 'tree', GRAPH 'graph' |
GraphInclude | DESCENDANTS 'descendants', ANCESTORS 'ancestors', BOTH 'both' |
Visibility | PUBLIC 'public', PRIVATE 'private' |
MimeTypeCategory | IMAGE, VIDEO, AUDIO, APPLICATION, TEXT |
The package also exports the request and response types used on this page,
such as TaruviResponse, UserData, FilterOperator, and StorageObject.