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.
Install
Section titled “Install”@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:
cd packages/kataflax_sdks/js && npm install && npm run buildQuickstart
Section titled “Quickstart”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,});Authentication
Section titled “Authentication”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 appssetToken stores a platform JWT that bypasses end-user rules, or a personal access token. Browser apps should use project login.
Send email
Section titled “Send email”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.
Query builder
Section titled “Query builder”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.
Bulk operations
Section titled “Bulk operations”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.
File upload
Section titled “File upload”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.
Errors
Section titled “Errors”try { await client.collection('posts').get('nonexistent');} catch (e) { if (e instanceof KataflaxException) { console.log(e.statusCode, e.message, e.errors); }}