Skip to content

JavaScript SDK

Package: @kataflax/sdk. Talks only to the data API (/api/p/:projectId/...). It does not manage orgs, schemas, or hosting. Use the admin SDK or the console for that.

Requires Node 18+ or a browser with fetch.

@kataflax/sdk is private ("private": true in its package.json). It is not on npm. The export map points at ./dist/index.js, and dist/ is gitignored, so a file: dependency works only after a local build.

From a monorepo app, depend on the package directory. Adjust file: to the app’s real relative path.

{
"dependencies": {
"@kataflax/sdk": "file:../kataflax_sdks/js/sdk"
}
}

Build once. That runs tsc in the package:

Terminal window
cd packages/kataflax_sdks/js && npm install && npm run build
import { KataflaxClient, KataflaxException } from '@kataflax/sdk';
const client = new KataflaxClient({
baseUrl: 'https://api.tefily.com',
projectId: 'YOUR_PROJECT_ID',
});
const auth = await client.auth.login({
email: 'user@example.com',
password: 'password',
});
console.log(`Logged in as ${auth.user['email']}`);
const posts = client.collection('posts');
const created = await posts.create({
title: 'Hello World',
status: 'draft',
});
const many = await posts.createMany([
{ title: 'Hello', status: 'draft' },
{ title: 'World', status: 'draft' },
]);
const all = await posts.list({ page: 1, pageSize: 20 });
const post = await posts.get(created['_id'] as string);
await posts.update(post['_id'] as string, { title: 'Updated title' });
await posts.delete(post['_id'] as string);
client.close();

Inject fetch in tests:

const client = new KataflaxClient({
baseUrl: 'https://api.tefily.com',
projectId: 'YOUR_PROJECT_ID',
fetch: mockFetch,
});
await client.auth.register({
email: 'jane@example.com',
password: 'secret123',
extraFields: { name: 'Jane' },
});
// Confirm the email via the Tefily link, then:
await client.auth.login({ email: 'jane@example.com', password: 'secret123' });
console.log(client.token);
client.setToken('eyJhbGciOi...'); // platform JWT or PAT. Not for browser apps

setToken stores a platform JWT that bypasses end-user rules, or a personal access token. Browser apps should use project login.

On a client with a serviceToken, send mail from the project’s own address. See Send email.

await backend.email.send({
to: 'jane@example.com',
subject: 'Your order shipped',
html: '<p>It is on the way.</p>',
replyTo: 'help@example.com',
});

Provide html, text, or both. The From address is fixed by the project.

End-user auth covers the confirmation email, password length, and the fact that register does not return a JWT.

auth.me(), resendVerification, verifyEmail, requestPasswordReset, and completePasswordReset are on auth. Argument shapes are on that page.

Service-token construction is on Service tokens. login on a client replaces whatever bearer was already set, including a service token, with the project JWT.

where takes an object of camelCase operators. Pass null when the filter must be JSON null (isEqualTo: null). Omitting the key omits the operator.

const results = await client
.collection('products')
.where('price', { isGreaterThanOrEqualTo: 10 })
.where('price', { isLessThan: 100 })
.where('category', { isIn: ['electronics', 'books'] })
.orderBy('price')
.orderBy('_createdAt', { descending: true })
.list({ pageSize: 50 });

Chained where is $and. orderBy('name') is ascending (1); descending: true is -1.

select(['title']) returns only those fields (plus _id).

join('authorId', { from: 'users', as: 'author' }) embeds matching related documents as an array under author (default limit 50, max 100).

expand('authorId') uses a reference field’s optional target collection.

select, join, and expand are chainable on Query (and as Collection entry points). Collection.get(id) returns the full document. Query.get(id) is the call that honors select, join, and expand.

Operator names are on Query, along with caps, how list rules apply to joins, and bulk validation.

updateMany and deleteMany require at least one where. They use the same encoded filter as list.

const modified = await client
.collection('posts')
.where('status', { isEqualTo: 'draft' })
.updateMany({ status: 'archived' });
const deleted = await client
.collection('posts')
.where('status', { isEqualTo: 'archived' })
.deleteMany();

Query documents the 100-item cap and the “at least one where” rule. createMany is in the quickstart.

uploadFile accepts Blob, ArrayBuffer, or Uint8Array.

const bytes = new Uint8Array(await file.arrayBuffer());
const meta = await posts.uploadFile(recordId, 'attachment', {
data: bytes,
size: bytes.byteLength,
filename: 'photo.jpg',
contentType: 'image/jpeg',
});
const download = await posts.getDownloadUrl(recordId, 'attachment', meta.id);
await posts.deleteFile(recordId, 'attachment', meta.id);

Files covers the presign, PUT, and complete steps, and the upload, download, and fileDelete endpoints.

try {
await client.collection('posts').get('nonexistent');
} catch (e) {
if (e instanceof KataflaxException) {
console.log(e.statusCode, e.message, e.errors);
}
}