Centrali JavaScript/TypeScript SDK¶
The Centrali SDK provides a simple, type-safe way to interact with Centrali's APIs from JavaScript or TypeScript applications. It handles authentication, request formatting, and response parsing automatically.
Installation¶
Quick Start¶
import { CentraliSDK } from '@centrali-io/centrali-sdk';
// Initialize the SDK
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'your-workspace',
// Option 1: Use an existing token (from user login)
token: 'your-bearer-token',
// Option 2: Use service account credentials (auto-fetches token)
clientId: 'your-client-id',
clientSecret: 'your-client-secret'
});
// Create a record
const product = await centrali.createRecord('Product', {
name: 'Wireless Headphones',
price: 99.99,
inStock: true
});
console.log('Created product:', product.data);
Authentication¶
The SDK supports two authentication methods:
1. Bearer Token (User Authentication)¶
For user-authenticated requests, provide a JWT token:
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'your-workspace',
token: 'jwt-token-from-login'
});
// Update the token later if needed
centrali.setToken('new-jwt-token');
2. Client Credentials (Service Account)¶
For server-to-server communication, use service account credentials:
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'your-workspace',
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
// The SDK automatically fetches and manages the token
// You can also manually fetch a token:
const token = await centrali.fetchServiceAccountToken();
API Reference¶
Initialization Options¶
interface CentraliSDKOptions {
/** Base URL of the Centrali API */
baseUrl: string;
/** Your workspace identifier */
workspaceId: string;
/** Optional bearer token for user authentication */
token?: string;
/** Optional service account client ID */
clientId?: string;
/** Optional service account client secret */
clientSecret?: string;
/** Optional custom axios configuration */
axiosConfig?: AxiosRequestConfig;
}
Records Management¶
Create a Record¶
const record = await centrali.createRecord('StructureName', {
field1: 'value1',
field2: 123,
nested: {
subField: 'value'
}
});
console.log('Created record ID:', record.id);
Get a Record¶
const record = await centrali.getRecord('StructureName', 'record-id');
console.log('Record data:', record.data);
Update a Record¶
const updated = await centrali.updateRecord('StructureName', 'record-id', {
field1: 'new value',
field2: 456
});
console.log('Updated record:', updated.data);
Delete a Record¶
Query Records¶
// Simple query with filters
const products = await centrali.queryRecords('Product', {
filter: 'inStock = true',
sort: '-createdAt',
limit: 10,
offset: 0
});
console.log(`Found ${products.data.length} products`);
// Query with complex filters
const orders = await centrali.queryRecords('Order', {
filter: 'status = "pending" AND total > 100',
sort: 'createdAt',
limit: 20
});
Bulk Operations¶
Get multiple records by their IDs:
const recordIds = ['rec-1', 'rec-2', 'rec-3'];
const records = await centrali.getRecordsByIds('Product', recordIds);
console.log(`Retrieved ${records.data.length} records`);
Compute Functions¶
Execute serverless compute functions:
// Invoke a function with payload
const result = await centrali.invokeFunction('calculate-discount', {
productId: 'prod-123',
quantity: 2,
couponCode: 'SAVE20'
});
console.log('Discount result:', result.data);
// Example: Send email via compute function
const emailResult = await centrali.invokeFunction('send-email', {
to: 'customer@example.com',
subject: 'Order Confirmation',
template: 'order-confirmation',
data: {
orderNumber: '12345',
items: [...]
}
});
File Uploads¶
Upload files to Centrali's storage service:
// In a browser environment
const fileInput = document.getElementById('file-input') as HTMLInputElement;
const file = fileInput.files[0];
const uploadResult = await centrali.uploadFile(
file,
'product-images', // folder/location
true // make publicly accessible
);
console.log('File URL:', uploadResult.data);
// In Node.js (with form-data library)
const FormData = require('form-data');
const fs = require('fs');
const form = new FormData();
form.append('file', fs.createReadStream('image.jpg'));
// Note: File upload in Node.js requires additional setup
Complete Examples¶
E-commerce Product Catalog¶
import { CentraliSDK } from '@centrali-io/centrali-sdk';
class ProductCatalog {
private centrali: CentraliSDK;
constructor() {
this.centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-store',
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
}
// Create a new product
async createProduct(productData: any) {
const product = await this.centrali.createRecord('Product', {
...productData,
createdAt: new Date().toISOString()
});
// Trigger inventory update
await this.centrali.invokeFunction('update-inventory', {
productId: product.id,
action: 'initialize',
quantity: productData.initialStock || 0
});
return product;
}
// Search products
async searchProducts(query: string, category?: string) {
const filter = category
? `(name CONTAINS "${query}" OR description CONTAINS "${query}") AND category = "${category}"`
: `name CONTAINS "${query}" OR description CONTAINS "${query}"`;
return await this.centrali.queryRecords('Product', {
filter,
sort: '-popularity',
limit: 20
});
}
// Get product with reviews
async getProductWithReviews(productId: string) {
// Get product
const product = await this.centrali.getRecord('Product', productId);
// Get reviews
const reviews = await this.centrali.queryRecords('Review', {
filter: `productId = "${productId}"`,
sort: '-createdAt',
limit: 10
});
return {
...product.data,
reviews: reviews.data
};
}
// Update inventory
async updateInventory(productId: string, quantity: number, operation: 'add' | 'subtract') {
const result = await this.centrali.invokeFunction('update-inventory', {
productId,
quantity,
operation
});
if (!result.data.success) {
throw new Error(`Inventory update failed: ${result.data.error}`);
}
return result.data;
}
}
// Usage
const catalog = new ProductCatalog();
// Create a product
const product = await catalog.createProduct({
name: 'Wireless Mouse',
description: 'Ergonomic wireless mouse with 6 buttons',
price: 29.99,
category: 'Electronics',
initialStock: 100
});
// Search products
const searchResults = await catalog.searchProducts('wireless', 'Electronics');
User Authentication Flow¶
import { CentraliSDK } from '@centrali-io/centrali-sdk';
class AuthService {
private centrali: CentraliSDK;
constructor() {
this.centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-app'
});
}
// Register a new user
async register(email: string, password: string, profile: any) {
// Create user record
const user = await this.centrali.createRecord('User', {
email,
profile,
createdAt: new Date().toISOString()
});
// Trigger welcome email
await this.centrali.invokeFunction('send-welcome-email', {
userId: user.id,
email
});
return user;
}
// Login (assuming you have a login endpoint that returns a JWT)
async login(email: string, password: string): Promise<string> {
// This would typically call your auth endpoint
const response = await fetch('https://auth.centrali.io/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const { token } = await response.json();
// Set the token for future SDK calls
this.centrali.setToken(token);
return token;
}
// Get user profile
async getUserProfile(userId: string) {
return await this.centrali.getRecord('User', userId);
}
// Update user preferences
async updatePreferences(userId: string, preferences: any) {
return await this.centrali.updateRecord('User', userId, {
preferences,
updatedAt: new Date().toISOString()
});
}
}
Real-time Data Sync¶
import { CentraliSDK } from '@centrali-io/centrali-sdk';
class DataSyncService {
private centrali: CentraliSDK;
private syncInterval: NodeJS.Timer | null = null;
constructor() {
this.centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-app',
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
}
// Sync local data with Centrali
async syncData(localData: any[]) {
const results = {
created: 0,
updated: 0,
errors: []
};
for (const item of localData) {
try {
if (item.centraliId) {
// Update existing record
await this.centrali.updateRecord('SyncedData', item.centraliId, item);
results.updated++;
} else {
// Create new record
const record = await this.centrali.createRecord('SyncedData', item);
item.centraliId = record.id; // Store ID for future syncs
results.created++;
}
} catch (error) {
results.errors.push({ item, error: error.message });
}
}
return results;
}
// Poll for changes
async pollChanges(since: Date, callback: (changes: any[]) => void) {
const changes = await this.centrali.queryRecords('SyncedData', {
filter: `updatedAt > "${since.toISOString()}"`,
sort: 'updatedAt',
limit: 100
});
if (changes.data.length > 0) {
callback(changes.data);
}
return changes.data;
}
// Start continuous sync
startSync(interval: number = 30000) {
let lastSync = new Date();
this.syncInterval = setInterval(async () => {
try {
await this.pollChanges(lastSync, (changes) => {
console.log(`Received ${changes.length} changes`);
// Process changes
});
lastSync = new Date();
} catch (error) {
console.error('Sync error:', error);
}
}, interval);
}
// Stop sync
stopSync() {
if (this.syncInterval) {
clearInterval(this.syncInterval);
this.syncInterval = null;
}
}
}
Error Handling¶
The SDK throws errors for failed requests. Always wrap API calls in try-catch blocks:
try {
const record = await centrali.createRecord('Product', productData);
console.log('Success:', record);
} catch (error) {
if (error.response) {
// API returned an error response
console.error('API Error:', error.response.data);
console.error('Status:', error.response.status);
if (error.response.status === 400) {
// Validation error
console.error('Validation failed:', error.response.data.error);
} else if (error.response.status === 401) {
// Authentication failed
console.error('Authentication required');
}
} else if (error.request) {
// Request was made but no response received
console.error('Network error:', error.message);
} else {
// Something else happened
console.error('Error:', error.message);
}
}
TypeScript Support¶
The SDK is written in TypeScript and provides full type definitions:
import {
CentraliSDK,
CentraliSDKOptions,
ApiResponse
} from '@centrali-io/centrali-sdk';
// Define your data types
interface Product {
id?: string;
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
}
// Use generic types for type-safe responses
const product = await centrali.createRecord<Product>('Product', {
name: 'Laptop',
price: 999.99,
description: 'High-performance laptop',
category: 'Electronics',
inStock: true
});
// TypeScript knows product.data is of type Product
console.log(product.data.price); // Type-safe access
Best Practices¶
1. Environment Variables¶
Store credentials securely:
// .env file
CENTRALI_BASE_URL=https://centrali.io
CENTRALI_WORKSPACE=my-workspace
CENTRALI_CLIENT_ID=your-client-id
CENTRALI_CLIENT_SECRET=your-client-secret
// Usage
const centrali = new CentraliSDK({
baseUrl: process.env.CENTRALI_BASE_URL,
workspaceId: process.env.CENTRALI_WORKSPACE,
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
2. Singleton Pattern¶
Create a single SDK instance for your application:
// centrali.ts
import { CentraliSDK } from '@centrali-io/centrali-sdk';
let instance: CentraliSDK | null = null;
export function getCentraliClient(): CentraliSDK {
if (!instance) {
instance = new CentraliSDK({
baseUrl: process.env.CENTRALI_BASE_URL!,
workspaceId: process.env.CENTRALI_WORKSPACE!,
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET
});
}
return instance;
}
// Usage in other files
import { getCentraliClient } from './centrali';
const centrali = getCentraliClient();
3. Retry Logic¶
Implement retry logic for transient failures:
async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Only retry on network errors or 5xx status codes
if (error.response && error.response.status < 500) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
throw new Error('Max retries exceeded');
}
// Usage
const record = await retryOperation(() =>
centrali.createRecord('Product', productData)
);
4. Batch Operations¶
Process large datasets efficiently:
async function batchCreate(records: any[], batchSize: number = 10) {
const results = [];
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
const promises = batch.map(record =>
centrali.createRecord('Product', record)
.catch(error => ({ error, record }))
);
const batchResults = await Promise.all(promises);
results.push(...batchResults);
}
return results;
}
Troubleshooting¶
Authentication Issues¶
// Check if token is valid
const token = centrali.getToken();
console.log('Current token:', token);
// Manually refresh service account token
if (centrali.options.clientId) {
const newToken = await centrali.fetchServiceAccountToken();
centrali.setToken(newToken);
}
Debug Logging¶
Enable axios debug logging:
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'my-workspace',
token: 'your-token',
axiosConfig: {
// Add request/response interceptors for logging
validateStatus: (status) => {
console.log('Response status:', status);
return status < 500;
}
}
});
Common Errors¶
| Error | Cause | Solution |
|---|---|---|
401 Unauthorized | Invalid or expired token | Refresh token or check credentials |
403 Forbidden | Insufficient permissions | Check workspace access rights |
404 Not Found | Invalid record ID or structure name | Verify the resource exists |
400 Bad Request | Invalid data format | Check field types and required fields |
429 Too Many Requests | Rate limit exceeded | Implement backoff and retry |
Migration from REST API¶
If you're currently using direct REST API calls, here's how to migrate:
Before (REST API)¶
// Direct API call
const response = await fetch('https://api.centrali.io/data/workspace/my-workspace/api/v1/records/slug/Product', {
method: 'POST',
headers: {
'Authorization': 'Bearer token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Product',
price: 99.99
})
});
const product = await response.json();
After (SDK)¶
// Using SDK
const product = await centrali.createRecord('Product', {
name: 'Product',
price: 99.99
});
Realtime Events¶
Subscribe to live record events using Server-Sent Events (SSE). The SDK handles connection management, authentication, and automatic reconnection.
Basic Usage¶
const subscription = centrali.realtime.subscribe({
structures: ['order'],
events: ['record_created', 'record_updated'],
onEvent: (event) => {
console.log('Event:', event.event, event.recordId);
console.log('Data:', event.data);
},
onError: (error) => {
console.error('Error:', error.code, error.message);
}
});
// Later: stop receiving events
subscription.unsubscribe();
Subscription Options¶
interface RealtimeSubscribeOptions {
// Filter by structure slugs (empty = all)
structures?: string[];
// Filter by event types (empty = all)
events?: ('record_created' | 'record_updated' | 'record_deleted')[];
// CFL filter expression for data filtering
filter?: string;
// Required: handle incoming events
onEvent: (event: RealtimeRecordEvent) => void;
// Optional callbacks
onError?: (error: RealtimeError) => void;
onConnected?: () => void;
onDisconnected?: (reason?: string) => void;
}
Event Payload¶
interface RealtimeRecordEvent {
event: 'record_created' | 'record_updated' | 'record_deleted';
workspaceSlug: string;
recordSlug: string; // Structure's slug
recordId: string;
data: object; // Record data
timestamp: string; // ISO timestamp
createdBy?: string;
updatedBy?: string;
}
Filtering Events¶
// By structure
centrali.realtime.subscribe({
structures: ['order', 'invoice'],
onEvent: handleEvent
});
// By event type
centrali.realtime.subscribe({
events: ['record_created'],
onEvent: handleEvent
});
// By data values (CFL filter)
centrali.realtime.subscribe({
structures: ['order'],
filter: 'data.status:shipped',
onEvent: handleEvent
});
// Combine filters
centrali.realtime.subscribe({
structures: ['order'],
events: ['record_updated'],
filter: 'data.total:gt:1000',
onEvent: handleEvent
});
React Integration¶
function OrderList() {
const [orders, setOrders] = useState<Order[]>([]);
useEffect(() => {
const centrali = new CentraliSDK({...});
// 1. Fetch initial data
centrali.queryRecords('order', { limit: 50 })
.then(res => setOrders(res.data));
// 2. Subscribe to updates
const sub = centrali.realtime.subscribe({
structures: ['order'],
onEvent: (event) => {
if (event.event === 'record_created') {
setOrders(prev => [event.data as Order, ...prev]);
} else if (event.event === 'record_updated') {
setOrders(prev => prev.map(o =>
o.id === event.recordId ? event.data.after : o
));
}
}
});
// 3. Cleanup
return () => sub.unsubscribe();
}, []);
return <ul>{orders.map(o => <li key={o.id}>{o.name}</li>)}</ul>;
}
Error Handling¶
centrali.realtime.subscribe({
onEvent: handleEvent,
onError: (error) => {
switch (error.code) {
case 'TOKEN_EXPIRED':
// Refresh token - SDK will reconnect
refreshToken().then(t => centrali.setToken(t));
break;
case 'RATE_LIMIT_EXCEEDED':
// Too many connections
showUpgradePrompt();
break;
case 'FORBIDDEN':
// No permission
showAccessDenied();
break;
}
}
});
For complete realtime documentation, see: - Realtime Quickstart - Realtime Authentication - Realtime Filtering - Realtime Troubleshooting
Related Documentation¶
- API Overview - Complete API reference
- Records API - Detailed records operations
- Realtime API - Realtime HTTP API reference
- Compute Functions - Writing serverless functions
- Authentication Guide - Authentication details
- Query Language - Advanced querying syntax
Support¶
For issues or questions: - Check the API Overview for endpoint details - Review error codes above - Contact support at support@centrali.io